浅析JavaScript中的装箱和拆箱
在javascript中有两种数据类型:
基本类型:字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol
引用类型:对象(Object)、数组(Array)、函数(Function)
在 JavaScript 中,有四个基本的包装类型 String、Number、Boolean、Symbol。
一、装箱操作
所谓的装箱,是指将基本数据类型转换为对应的引用类型的操作。
装箱分隐式装箱和显式装箱两种装箱方式。
1、隐式装箱
先说「隐式装箱」,隐式装箱是由「引擎自动执行」的。
let web = 123;
基本类型是不能添加属性和方法的,添加会报错。
let web = 'Javascript';
web.subText = 'JavaScriptSub';
web.subTextFn = function(){
console.log('JavascriptSubTextFn');
};
console.log(web.subText);//undefined
console.log(web.subTextFn());//Uncaught TypeError: web.subTextFn is not a function
那为什么普通字符串类型可以调用方法呢,比如str.substring()、str.indexOf()等,我们接着往下看:
2、那装箱都做了什么?
在读取值的时候,引擎会创建一个基本类型所对应的「包装类型的对象」,见下图。
对于隐式装箱的执行步骤,我们看下面的代码:
var s1 = 'call_me_R'; // 隐式装箱
var s2 = s1.substring(2);
上面代码的执行步骤其实是这样的:
(1)先创建String类型的一个实例;
(2)在实例中调用制定的方法;
(3)销毁这个实例。
上面的三个步骤转换为代码,如下:
// 1
var s1 = new String('call_me_R');
// 2
var s2 = s1.substring(2);
// 3
s1 = null;
所以,我们在基本类型值上可以使用方法(比如string的substring等),是因为有「隐式装箱」操作。
隐式装箱当读取一个基本类型值时,后台会创建一个该基本类型所对应的基本包装类型
对象。在这个基本类型的对象上调用方法,其实就是在这个基本类型对象上调用方法。这个基本包装类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立即被销毁。这也是在基本类型上添加属性和方法会不识别或报错的原因了。
3、显式装箱
装箱的另一种方式是显式装箱,这个就比较好理解了,这是通过基本包装类型
对象对基本类型进行显式装箱,如下:
var name = new String('call_me_R');
显式装箱的操纵可以对new
出来的对象进行属性和方法的添加啦,因为通过new操作符创建的引用类型的实例,在执行流离开当前作用域之前一直保留在内存中
。
var objStr = new String('call_me_R');
objStr.job = 'frontend engineer';
objStr.sayHi = function(){
console.log('hello kitty');
}
console.log(objStr.job); // frontend engineer
objStr.sayHi(); // hello kitty
显式装箱可以添加属性和方法的,隐式装箱是不能添加属性和方法的。
二、拆箱操作
拆箱就和装箱相反了,拆箱是指把引用类型转换成基本的数据类型。通常通过引用类型的valueOf()和toString()
方法来实现。
引用类型的值转成基本类型的值就是拆箱。拆箱必须要提两个方法 toString() 和 valueOf() ,toString() 返回字符串,valueOf() 返回对象本身。
1、在下面的代码中,留意下valueOf()和toString()
返回值的区别:
var objNum = new Number(64);
var objStr = new String('64');
console.log(typeof objNum); // object
console.log(typeof objStr); // object
// 拆箱
console.log(typeof objNum.valueOf()); // 64 number 基本的数字类型,想要的
console.log(typeof objNum.toString()); // '64' string 基本的字符类型,不想要的
console.log(typeof objStr.valueOf()); // '64' string 基本的数据类型,不想要的
console.log(typeof objStr.toString()); // '64' string 基本的数据类型,想要的
2、更改对象的 toString() 和 valueOf() 两个方法:
//更改对象的 toString() 和 valueOf() 两个方法:
var web = {
'name':'html',
valueOf: () => {
console.log("valueOf");
},
toString: () => {
console.log("toString");
}
}
console.log(String(web))//toString undefined
console.log(Number(web))//valueOf NaN
var ss = {'web': 22}
String(ss)
//"[object Object]"
ss.toString()
//"[object Object]"
ss.valueOf()
//{web: 22}
Number(ss)
//NaN
执行的逻辑是什么?为什么有时候先走 valueOf() ?为什么有时候先执行 toString()?
对象中有 toPrimitive 方法,此方法提供了用于将对象强制转换为基元并替换 toString() 和 valueOf() 方法的通用接口。
运行方法时,判断 PreferredType 的值是哪种类型:
如果是 Number:
(1)如果是基本类型,按原样返回。
(2)否则,输入是一个对象,调用 obj.valueOf() 方法,如果结果是原始的,则将其返回。
(3)否则,调用 obj.toString() 方法,如果结果是原始数据,则将其返回。
(4)否则,抛出 TypeError
如果为 String:那需要交换 2 和3 的执行顺序。
如果没有 PreferredType ,对于 Date 的实例将其设置为 String。
对于所有的其他值,设置为 Number。
参考资料:
https://juejin.cn/post/6844903859765133320
https://mp.weixin.qq.com/s/QDHJBCP9E8GyKPlMOExwAA