V8如何存储Fast Objects?及对象增长时重哈希的后续问询
Fast Objects之所以能实现超快的属性访问,核心就靠Hidden Class(隐藏类)和内联属性存储的组合拳,我给你一步步拆解细节:
1. Hidden Class:对象的「结构蓝图」
当你创建一个空对象const obj = {}时,V8会立刻给它分配一个初始的Hidden Class(咱们姑且叫它C0)。这个Hidden Class就像一份严格的模板,记录着对象当前的属性结构——包括属性名称、添加顺序、对应的存储位置等关键信息。
每给对象新增一个属性,V8不会直接修改原有的Hidden Class,而是生成一个全新的Hidden Class,同时在新旧两个Hidden Class之间建立一条「转换路径」。比如:
- 给
obj加obj.a = 1,会生成C1,C0到C1的转换规则就是「添加属性a」 - 再给
obj加obj.b = 2,会生成C2,C1到C2的转换规则就是「添加属性b」
这种设计让V8能快速追踪对象结构的变化,而且结构完全相同的对象会共享同一个Hidden Class,大大节省内存开销。
2. Fast属性的存储:直接内存偏移访问
Fast模式下,对象的属性值根本不是存在对象自身的哈希表里,而是存在**独立的属性存储槽(slots)**中,Hidden Class则负责记录每个属性对应的槽位偏移量。
举个实际的例子:
- 当
obj有a和b两个属性时,Hidden ClassC2会明确记录:a对应偏移量0,b对应偏移量1 - 当你访问
obj.a时,V8先找到obj绑定的Hidden Class,查到a的偏移量,直接去对应的存储槽取值——这就是纯纯的直接内存访问,速度比哈希表查找快了不止一个量级
这里要划个重点:属性的添加顺序会直接影响Hidden Class。比如先加a再加b的对象,和先加b再加a的对象,哪怕属性完全一样,也会拥有不同的Hidden Class。
3. 从Fast到Slow的切换:什么时候会触发?
V8不会一直硬撑Fast模式,当对象的结构变得过于「混乱」时,就会自动切换到Slow模式(哈希表存储),常见的触发场景有:
- 频繁添加、删除不同名称的属性,导致Hidden Class的转换链过长
- 使用
delete关键字删除对象属性(这会直接破坏结构的稳定性) - 动态添加大量属性,超过了V8预设的Fast属性数量阈值
- 用
Object.defineProperty等方法修改属性特性(比如从可枚举改为不可枚举)
一旦切换到Slow模式,对象的属性就会被存入一个名为Dictionary的哈希表中,每次访问都要计算哈希值、处理哈希冲突,速度会慢很多,但胜在灵活性更高。
4. 额外优化:Hidden Class的共享与缓存
V8会尽可能让结构一致的对象共享Hidden Class,比如:
const obj1 = {a: 1, b: 2}; const obj2 = {a: 3, b: 4};
obj1和obj2会共享同一个Hidden Class,因为它们的属性顺序和名称完全匹配。这不仅省内存,还能让JIT编译器生成更高效的机器码——编译器可以基于Hidden Class的固定结构,直接生成偏移量访问的代码,不用每次都做类型检查。
另外,V8还会对Hidden Class的转换链做优化,如果发现某些转换路径被频繁使用,会直接缓存对应的转换结果,减少不必要的计算开销。
内容的提问来源于stack exchange,提问作者Lance Pollard

