v8中属性存储采用“线性数组”与“哈希表”动态切换机制:新对象预分配in-object槽位存放构造时已知属性,其余属性按序存入properties线性数组;当动态增删、属性名不可静态推断或总数超阈值时,自动退化为哈希表模式以提升灵活性但降低访问性能。

理解 V8 中属性存储的“线性数组”与“哈希表”切换,关键不在“有没有原型”,而在于对象自身属性的数量、添加顺序和稳定性。原型链本身不直接触发切换,但它会影响隐藏类(Map)的演化路径,间接决定何时退化为哈希表。
线性数组(in-object + properties array)是怎么工作的
V8 为新对象预分配一段紧凑内存(in-object slots),用于存放构造时已知的属性(如 this.a = 1; this.b = 2;)。剩余非数值索引属性则按插入顺序追加到 properties 数组中——这个数组就是所谓的“线性数组”。它支持 O(1) 索引访问,前提是属性名与下标映射关系固定且可预测。
- 属性必须按固定顺序添加(比如都在构造函数里赋值)
- 不能动态删除或新增未声明的属性(否则破坏顺序)
- 隐藏类会记录每个属性在 in-object 或 properties 中的具体偏移量
什么情况下会退化成哈希表(dictionary mode)
一旦对象出现以下任一行为,V8 就会将其 properties 从线性数组升级为字典模式(哈希表):
- 运行时动态添加大量属性(比如
obj['key' + i] = val) - 频繁增删属性(尤其是 delete 操作)
- 属性名无法静态推断(如通过变量拼接、JSON.parse 后赋值)
- 属性总数超过 V8 内部阈值(通常几十个,具体版本有差异)
此时,所有自有属性都存进一个哈希表,查找靠哈希计算+链地址法,不再依赖顺序或偏移。性能下降但灵活性提升。
原型继承如何间接影响这个切换
原型本身不存于 properties,所以继承来的属性不会计入线性数组容量。但原型链会干扰隐藏类收敛:
- 如果子对象在不同原型链上反复添加同名属性(比如多个构造函数共享原型但各自扩展),V8 难以复用统一隐藏类
- 使用
Object.create(null)或手动修改__proto__,容易导致隐藏类分裂,加速字典模式降级 - 从原型读取属性(
obj.x)不触发切换;但从原型写入(obj.x = 1)是向自身添加属性,可能打破原有线性结构
实际开发中可以注意的点
想保持高性能属性访问,应尽量让对象“结构稳定”:
- 在构造函数中一次性声明并初始化所有预期属性(哪怕初始值为
undefined) - 避免对同一对象混用字面量键和动态计算键
- 不要在热路径中频繁
delete属性或遍历for...in(会强制进入字典模式) - 继承时优先用
class语法或标准Object.setPrototypeOf,少用非标准方式篡改原型










