林秀栋的技术博客

类型转换的装箱,拆箱

文章来源 文章来源

JS中目前共有7种数据类型:Undefined、Null、Boolean、Number、String、Symbol 和 Object。

前 6 者是基本类型数据(直接栈内保存),Object 是引用类型数据(在堆中生成,在栈内保存的是该对象的引用)。

JS 是一门弱类型语言。它不需要事先具体声明变量的类型,在程序运行过程中,类型会被自动推断确定。因此,可以用同一个变量保存不同类型的数据:

var a = 1;
a = 'abc';
a = {
  x: 1
};

装箱

除此之外,还有一点不同就是:基本类型数据本身不可变,但引用类型数据是可变的。

引用类型数据是可变的,不言而喻,是指我们可以修改其属性值。

var a = {
    x: 1
};
a.x = 2;

console.log(a); // => {x: 2}  
console.log(a.x); // 2

然而在 JS 中基本类型变量也是可以使用“点”的,比如:

var a = 1;
a.x = 2;

console.log(a);// 1  
console.log(a.x);// undefined

上述代码运行过程中发生了所谓的“装箱”操作。

a.x = 2
// 等价于
var temp = new Number(a)
temp.x = 2
temp = null

因为 2 是基本类型,在取其属性时,先用对应的 Number 构造函数包裹成一个临时对象,然后再对临时对象取属性值操作,随后这个临时对象便销毁。

整个过程称为“装箱”。知道这个过程后,那么整段代码就好理解了:

var a = 1;
var temp1 = new Number(a);
temp1.x = 2;
temp1 = null;

console.log(a);// 1  

var temp2 = new Number(a);

console.log(temp2.x);// undefined  temp2 = null;

两次 a.x,有两次“装箱”,因此是两个不同的临时对象。给一个对象存值,然后再去另外一个对象那里取值,必然是取不到的。

参考JS 文档规范,以“[]”属性访问方式为例

这里再具体举一个例子,来看看整个流程。比如,a = 1,然后获取 a[‘x’] 。

  1. 计算表达式 a。(这里用表达式是考虑到像这种情形:a.b[‘x’],此时表达式是 a.b)
  2. 获取上一步结果的值,这里是1。
  3. 计算表达式 ‘x’。(这里用表达式是考虑到像这种情形:a[‘x’+’y’],此时表达式 ‘x’+’y’)
  4. 获取第 3 步的结果,即 ‘x’。
  5. 把第 2 步的结果传入 ToObject,即 ToObject(1)。这里记做 temp
  6. 把第 4 步,转化为字符串,当然还是 ‘x’
  7. 返回,temp[‘x’]

其中第 5 步,最为关键。它用 ToObject 生成个临时对象(因为它只是局部变量),我们最后取到的属性值正是这个对象的属性值。

ToObject 的具体操作为

输入 输出
undefined 报错
null 报错
boolean Create a new Boolean object
number Create a new Number object
string Create a new String object
object 返回输入内容本身

拆箱

对引用类型进行那些基本类型“才该有的”操作,即“拆箱操作”。

var a  = 1;
var b = {};
console.log(a - b);

对普通对象进行减法操作时,对象需要转化为数字类型。这时调用了内部操作 ToNumber

ToNumber 的具体操作为

输入 输出
undefined NaN
null +0
boolean true => 1, false => +0
number 返回输入内容本身
string See grammar and note below
object 1. ToPrimitive 2. ToNumber

由上文可知,对object进行ToNumber操作时,会经历两步,1. ToPrimitive 2. ToNumber

ToPrimitive(转为基本类型)的具体操作为

输入 输出
undefined 返回输入内容本身
null 返回输入内容本身
boolean 返回输入内容本身
number 返回输入内容本身
string 返回输入内容本身
object 获取对象的默认值。使用内部的[[DefaultValue]](hint)

对于 [[DefaultValue]](hint), 根据 hint 值采取不同的处理方式

比如 hint 是 String 时,优先调用对象的 toString 方法,如果返回值是基本类型值,返回该值,否则调用对象的 valueOf 方法,如果返回值是基本类型值,返回该值。否则报错。

而 hint 是 Number 时,顺序是反过来的,优先调用 valueOf,如果其返回值不是基本类型,再调用 toString。

另外,除了日期对象外,如果没传 hint 的话,其默认值是 Number,因此 JS 中类型转化时,更偏爱 Number。

下面我们举几个例子看看:

var a = {  
    toString() {    
        return 3  
    },  
    valueOf() {    
        return '30'  
    }
};
console.log(a - 5); // 25

这里使用的是减法操作,此时 hint 是 Number,因此先调用对象 a 的 valueOf 方法,其返回值 ‘30’ 是字符串类型,是基本类型。因此 a - 5 变成了 ‘30’ - 5。

再看:

var a = {  
    toString() {    
        return {}  
    },  
    valueOf: null
};
console.log(a - 5); // Uncaught TypeError: Cannot convert object to primitive value

对象 a,其方法 valueOf 不是函数,因而看其 toString 方法,而该方法返回的是一个空对象,不是基本类型。因而报错。