likes
comments
collection
share

包装类的详细解析原理实现与引擎分析——装箱机制

作者站长头像
站长
· 阅读数 61

前言

在JavaScript的世界里,数据类型分为两大阵营:简单数据类型(原始类型)与复杂数据类型(对象类型)。简单数据类型包括numberstringbooleanundefinednullsymbolbigint,而复杂数据类型则涵盖了所有其他类型,如对象、数组、函数等。尽管简单数据类型在内存中以值的形式存储,不支持直接的属性或方法添加,JavaScript引擎却巧妙地引入了包装类的概念,使简单数据类型在需要时表现出对象的特性。


包装类概述

包装类是JavaScript中一种特殊的对象类型,用于“包裹”简单数据类型,使其在需要时能以对象的形式存在。每当对简单数据类型执行方法调用或属性访问时,JavaScript引擎会自动创建一个对应的包装类实例,执行相关操作后立即将其销毁。这一过程对开发者是透明的,但却是实现简单数据类型方法调用的关键。

正文

对于这段代码你是怎么想它的运行过程的是怎么样的?报错?undefined?还是什么?

let num=1
num.a=2
console.log(num.a) 

这个问题我们放在文章后面进行分析,我们先来复习一下类的引用操作

类的引用操作

var mrPeng={
  name:'彭于晏',
  age:18,
  sex:'男',
  }

我们创建了一个mrPeng的类,在这里我们怎么去对键值来应用呢,给出下面三个方法:

  • Object.sex=? 正常查找键名为sex属性
  • Object[sex]=? 查找变量sex代表值与Object键名相同的属性
  • Object['sex']=? 正常查找键名为sex属性

我们常用的是第一种查找方式,但是对于使用类似于数组查找的这种查找方式确是很多人所模糊的,这里我们记住如果在[]内部是内没加''的,那么在执行这句语句时,内部的名称是会被看作是一个变量名从而去查找它的值。选择哪种方式取决于你是否需要动态确定键名,或者是否希望键名包含特殊字符。直接属性访问适用于静态已知的属性名,而计算属性访问则适用于动态或含有特殊字符的键名。


  • 直接属性访问 (Object.sex) : 尝试访问预定义或自定义在Object原型链上的属性,如果属性不存在则返回undefined
  • 计算属性访问 (Object[sex]) : 首先解析sex变量,使用其值作为键名来访问Object对象。如果sex变量未定义,则抛出引用错误;如果sex变量的值不是有效的键名(例如,包含空格或其他非合法字符),则将无法找到相应的属性(undefined)。
  • 字符串键名访问 (Object['sex']) : 直接指定键名来访问属性,不会解析额外的变量,即使属性不存在也安全返回undefined

类的创建方式

  • var Obj1={} //创建对象字面量
  • var Obj2=new Object(); //使用new Object()构造函数
  • new XXX() //XXX为构造函数 三种不同的类的创建方式

这里面对象字面量是创建对象最简洁、最常用的方式。通过一对大括号{},可以直接定义对象及其属性和方法。而通过new关键字调用Object构造函数。这种方式创建的对象与通过对象字面量创建的对象本质上是一样的,但在实际开发中较少使用,因为它比对象字面量冗长且不易读。使用自己的构造函数,用于创建具有特定属性和方法的对象。这非常适用于创建多个具有相同结构的对象。

记住在实际开发中单个类的创建我们一般都是才有var obj={}这种方式来创建的。

包装类

讲完了类的几个属性,我们开始回到主体,看下面这段在浏览器中的执行代码:

包装类的详细解析原理实现与引擎分析——装箱机制

怎么会这样呢?因为在JavaScript中,Number既是一个内置的构造函数,也是一个类型。当你使用new Number(10)创建变量a时,你实际上是在使用Number构造函数来创建一个对象,这个对象封装了一个数值。因此,a是一个Number类型的对象,所以typeof(a)返回的是'object'。 另一方面,当你简单地声明let b = 10时,你创建的是一个原始数值(即简单数据类型),在这种情况下,b就是一个数值(number类型),因此typeof(b)返回的是'number'这里的关键区别在于使用new关键字创建的是一个对象,而直接赋值一个数值则是创建一个简单的数值类型。

更详细的解释

当你使用new Number()构造函数时,JavaScript创建了一个Number对象,这个对象包含了你传递给构造函数的数值。这种对象在内部存储了数值,但是它是一个完整的对象,具有对象的所有特性,包括方法和属性。例如,a可以调用Number对象的内置方法,如toFixed(),这是因为a实际上是一个对象。

包装类的详细解析原理实现与引擎分析——装箱机制

那为什么b可以调用tofixed()呢,而且却也是10.0呢?不是说只有对象身上才可以携带属性和方法吗?

装箱机制

在JavaScript中,装箱机制是一种运行时的自动转换过程,它允许原始数据类型(如numberstringboolean)在需要时被转换成对应的对象类型(如NumberStringBoolean对象)。这种机制使得原始数据类型可以像对象一样被调用方法或访问属性。

装箱机制的工作原理

当JavaScript引擎检测到对一个原始数据类型值进行方法调用或属性访问时,它会自动创建一个对应的包装对象,然后调用该对象上的方法或属性。一旦方法调用完成或属性访问结束,这个临时创建的包装对象就会被销毁,以防止不必要的内存占用。


因此我们在对b进行.toFixed操作时没有进行报错

这里我们注重分析这一句:“一旦方法调用完成或属性访问结束,这个临时创建的包装对象就会被销毁”什么叫做会被销毁,也就是当我们在原始数据类型身上挂载属性和方法时没问题的,例如分析

console.log(b.toString(1));

由于装箱机制的存在,V8引擎是不会报错的,引擎做了以下事情:

  1. 装箱:引擎自动创建了一个Number对象,将b的值(10)作为参数传递给Number构造函数,这样b就被临时封装成了一个Number对象。
  2. 方法调用:然后,toString方法在这个临时的Number对象上被调用,该方法将数值转换为字符串。
  3. 解包:方法调用完成后,临时创建的Number对象被销毁,b变量仍然保持其原始的数值类型。

总结

现在再回过头来看这道题,相信你已经有结果了,答案是undefined。

let num=1
num.a=2
console.log(num.a) 

因为在你给num添加a属性为2时,引擎自动创建了一个Number对象,将num的值(2)作为参数传递给Number构造函数,这样num就被临时封装成了一个Number对象。然后,添加a属性等于2在这个临时的Number对象方法调用完成后,临时创建的Number对象被销毁,num变量仍然保持其原始的数值类型。 当你再去打印num.a时,又会自动创建了一个Number对象,这时这个Number对象中去查找a属性是一个没有的属性,返回的就是undefined。


虽然装箱机制使得在原始数据类型上使用对象方法变得方便,但也有一些需要注意的地方:

  • 性能开销:每次装箱和解包都会产生一定的性能开销,特别是在循环或频繁调用的情况下。
  • 内存消耗:虽然装箱的对象是临时的,但如果频繁进行装箱操作,可能会增加垃圾回收的压力。
  • 避免修改:不要尝试在原始数据类型上添加持久性的属性或方法,因为这些更改不会保留,一旦装箱操作结束,这些属性或方法就会丢失。
转载自:https://juejin.cn/post/7376186315643584521
评论
请登录