JS:装箱与拆箱

2,760 阅读3分钟

概览

  • 我们可以在基本类型值(比如字符串 "Hello")上访问属性 & 调用方法,但
  • 基本类型值并不支持属性和方法设置。能这样做的原因,
  • 是因为引擎内部做了装箱与拆箱操作

carbon (2).png

正文

基本类型与复杂类性

JavaScript 中值的类型有两种:基本类性值(primitives)和复杂类性值(即对象,objects)。

基本类型存储的是一个简单值;对象则使用引用访问,我们通过引用里记录的地址,找到对象数据在内存中实际存储的位置,并进行操作。这是两种类性值本质上不同的地方。

调用方法和访问属性是对象上所特有的访问数据的方式。

let num = new Number(1234.236)

// 访问属性
typeof num.toLocaleString // "function"
// 调用方法
num.toLocaleString() // "1,234.236"

但会发现,直接在 1234.236 上进行同样是 OK 的。

image.png

奇怪吧,原始类性值居然也有属性和方法?不然,其实 JS 引擎背地里做了操作:装箱和拆箱。

装箱和拆箱

装箱就是把原始类型值转为对应的包装对象,比如:

let num = 1234.236
// 数值包装成 Number 对象
new Number(num)

let str = 'Hello'
// 字符串包装成 String 对象
new String(str)

num 转为 new Number(num)str 转为 new String(str) 等。当然,这个过程对我们来说是不可见的。也正是因为 JS 内部做了如此的转换,我们才得以能够“看起来可以直接在原始类型值访问属性和调用方法”。

当然,装箱使用完毕后,还有一个拆箱的过程。所谓拆箱,就是把包装对象转为对应的原始类型值表现形式。

// 将 new Number 拆箱成 1234.236
new Number(num).valueOf() // 1234.236

// 将 new String 拆箱成 Hello
new String(num).valueOf()

也就是说下面代码的执行过程:

image.png

做了类似于如下的处理:

// 装箱:转换成对应的包装对象
num = new Number(1234.236)

// 使用
typeof num.toLocaleString // "function"
num.toLocaleString() // "1,234.236"

// 拆箱
num = num.valueOf()

这就是为什么会有“原始类型值方法”出现(这里加了引号,说明是假的,只是一种表象而已)。

为什么要这么做呢?

之所以这么做,肯定是因为这种处理方式利大于弊,可以简单总结为:

  • 方便。操作基本类性值的场景还是比较多的。如果每次为了使用属性或者调用方法之前都要包装一次,未免太过麻烦。
  • 省内存。大家知道存储同一个数据(比如 new Number(1)1 ),对象对内存的开销比基本类型值要大。有了拆装箱的操作,我们只在使用的时候暂时包装成对象访问,其余时间还是以基本类型值的形式存在,能够节省不少的内存。

另外,我们其实不用担心这种方式的开销问题。因为 JS 自打出生就支持这个拆装箱特性了,内部也对这类操作做了优化,所以性能问题几乎可以忽略。

null/undefined 没有对应的包装函数

JS 当前一共有 7 种基本类型值:字符串、数值、大整数(bigint)、布尔、Symbol、null 还有 undefined.

除了 nullundefined 之外,其他基本类型都要对应的包装函数。因此在 null  和 undefined 上访问属性是会报错的。

alert(null.test); // error

这也是能够理解的,undefined 表示变量未初始化,null 表示变量为空。两者都没有什么实际的数据意义,因此没有对应的包装函数。

参考链接

(正文完)


广告时间(长期有效)

我有一位好朋友开了一间猫舍,在此帮她宣传一下。现在猫舍里养的都是布偶猫。如果你也是个猫奴并且有需要的话,不妨扫一扫她的【闲鱼】二维码。不买也不要紧,看看也行。

(完)