V8引擎如何区分原始值(Primitive)与对象(Object)?
我在StackOverflow上看到一个回答提到:“一般来说,在JavaScript中,所有值、所有对象及其所有属性都存储在堆上。”随后查阅V8官方博客内容,其中提到:“V8中的JavaScript值都表示为对象并分配在V8堆上,无论它们是对象、数组、数字还是字符串。这使得我们可以将任何值表示为指向对象的指针。”另外,《Speaking JavaScript》一书中对原始值和对象的定义如下:
原始值
原始值包括:布尔值(true、false)、数字(如1736、1.351)、字符串(如'abc'、"abc")以及两个「非值」undefined、null。其特性为:按值比较、始终不可变。
对象
所有非原始值都是对象,常见类型包括普通对象、数组、正则表达式等。其特性为:按引用比较、默认可变。
我的疑问是:引擎必须知晓值是原始值还是对象,才能应用对应的特性。既然V8引擎将所有值都表示为指向对象的指针,那它是如何区分原始值与对象的?
我个人猜想:指针(引用)的一部分包含原始值本身,若该部分为空则指向对象;或者指针中存在标记位,用于标识指向的是原始值还是对象。
你的猜想方向完全正确,V8正是通过指针标记位结合原始值直接嵌入的方式来区分,核心逻辑如下:
指针低位的标记位
V8的64位指针会利用末尾1-3个二进制位作为类型标记:- 若末尾标记位为0,代表这是指向堆中普通对象的真实指针;
- 若标记位为特定非0值,则代表这是原始值,不同标记组合对应布尔、整数、浮点数等不同原始类型。
小原始值的直接嵌入(Tagged Pointer)
对于能放进指针剩余位宽的小值(比如32位整数、布尔值),V8会直接把值存在指针的高位部分,无需去堆内存寻址。比如64位指针去掉2位标记位后,剩下的62位足够存储常规整数,这类值完全不需要指向堆对象。特殊原始值的专属标记
undefined、null这类特殊值有专门的指针标记:比如null对应全0的指针(配合标记位),undefined有独立的标记值,完全不需要关联堆中的任何对象。
对于字符串、大数字这类无法嵌入指针的原始值,V8会在堆中创建对应的「包装对象」,但会给这类对象打上特殊标记,引擎能识别出这是原始值的包装,而非普通对象,从而保证按值比较、不可变等原始值特性正常生效。
简单来说,V8靠指针里的标记位快速判断类型,小原始值直接存在指针里,大原始值用带特殊标记的堆对象,以此在底层区分原始值和普通对象,同时符合JavaScript层面的特性要求。
内容的提问来源于stack exchange,提问作者user17597436

