在 JavaScript 中,有时会碰到以下两种类型的代码:
var num = 123; var str = '123'; num.toFixed(2); // '123.00' str.split(''); // ['1','2','3']
var num = [1,2,3]; var str = { name:'hahaha' }; console.log(num+1); //1,2,31 String(str); //'[object Object]'
在第一段代码中,我们在两个基本类型的值上调用方法。众所周知,在 JavaScript 中只有 Object 类型才有方法。在第二段代码中,我们可以把两个 Object 类型的值当做基本类型的值直接使用。这就是 JavaScript 装箱与拆箱在代码中的具体体现。
下面我分别来解释一下 JavaScript 中的装箱与拆箱。
一:装箱
所谓装箱,就是把基本类型转变为对应的对象。装箱分为隐式和显示。
隐式装箱
每当读取一个基本类型的值时,后台会创建一个该基本类型所对应的对象。在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法。这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁。具体到代码如下:
num.toFixed(2); // '123.00'
//上方代码在后台的真正步骤为
var c = new Number(123);
c.toFixed(2);
c = null;
当我们访问 num 时,要从内存中读取这个数字的值,此时访问过程处于读取模式。在读取模式中,后台进行了三步处理:
- 创建一个 Number 类型的实例。
- 在实例上调用方法。
- 销毁实例。
显式装箱
通过内置对象 Boolean、Object、String 等可以对基本类型进行显示装箱。
var obj = new String('123');
二:拆箱
拆箱与装箱相反,把对象转变为基本类型的值。拆箱过程内部调用了抽象操作 ToPrimitive 。该操作接受两个参数,第一个参数是要转变的对象,第二个参数 PreferredType 是对象被期待转成的类型。第二个参数不是必须的,默认该参数为 number,即对象被期待转为数字类型。有些操作如 String(obj) 会传入 PreferredType 参数。有些操作如 obj + " " 不会传入 PreferredType。
具体转换过程是这样的。默认情况下,ToPrimitive 先检查对象是否有 valueOf 方法,如果有则再检查 valueOf 方法是否有基本类型的返回值。如果没有 valueOf 方法或 valueOf 方法没有返回值,则调用 toString 方法。如果 toString 方法也没有返回值,产生 TypeError 错误。
PreferredType 影响 valueOf 与 toString 的调用顺序。如果 PreferrenType 的值为 string。则先调用 toString ,再调用 valueOf。
具体测试代码如下:
var obj = {
valueOf : () => {console.log("valueOf"); return []},
toString : () => {console.log("toString"); return []}
}
String(obj)
// toString
// valueOf
// Uncaught TypeError: Cannot convert object to primitive value
obj+' '
//valueOf
//toString
// Uncaught TypeError: Cannot convert object to primitive value
Number(obj)
//valueOf
//toString
// Uncaught TypeError: Cannot convert object to primitive value