Skip to content

V8 对象属性存储:快属性与慢属性

在 JavaScript 中,对象是属性的集合。但 V8 为了优化不同场景下的属性访问,在底层将属性分为了多种存储模式。

一、V8 对象结构图解

V8 将对象的属性分为 Elements (索引属性)Properties (命名属性),并针对它们分别存储。

V8 对象属性存储模型

  • Map (隐藏类): 指向对象的元数据,描述其内存结构。
  • Elements: 存储数组索引属性(如 obj[1], obj[2]),存储在连续的线性数组中,方便通过索引直接访问。
  • Properties: 存储命名属性(如 obj.a, obj.b)。

二、属性的存储方式

1. 对象内属性 (In-Object Properties)

V8 会在对象本身的内存空间中预留几个位置,直接存储最常用的属性。这是速度最快的存储方式,因为不需要额外的寻址。

2. 快属性 (Fast Properties)

当对象内属性位置占满后,多出的属性会存储在一个额外的 线性数组 中。

  • 查找方式: 结合隐藏类记录的偏移量,直接在数组中按索引取值。
  • 特点: 访问速度依然很快,但相比对象内属性多了一次寻址开销。

3. 慢属性 (Slow / Dictionary Mode)

当属性数量过多,或者对对象使用了 delete 操作导致结构破坏时,V8 会将属性存储切换为 哈希表 (Dictionary)

  • 查找方式: 通过属性名的哈希值在字典中查找。
  • 特点: 访问速度显著变慢,不再依赖隐藏类,也无法享受内联缓存优化。

三、为什么要区分 Elements 和 Properties?

主要原因在于 属性遍历顺序 的规范要求:

  1. ECMAScript 规范要求:
    • 所有的索引属性按升序排列。
    • 所有的命名属性按添加顺序排列。
  2. V8 的策略: 为了满足规范并保持高效,V8 将它们分开存储。索引属性存储在 Elements 数组中,命名属性存储在 Properties 数组或对象内。

四、高频面试题

1. 解释 V8 中的 Elements 和 Properties 有什么区别?

回答

  • Elements: 专门存储数组索引属性。为了满足升序排列的要求,V8 使用线性数组存储,通过索引直接定位,效率极高。
  • Properties: 存储非索引的命名属性。它们又分为对象内属性(最快)、快属性(线性存储)和慢属性(哈希存储)。

2. 为什么在 JS 中属性的遍历顺序有时是固定的,有时又不是?

回答:根据规范,索引属性(Elements)总是按数值升序遍历,而命名属性(Properties)按添加顺序遍历。V8 底层将它们分开存储就是为了高效地实现这一规范要求。

3. 什么情况下对象会从“快模式”进入“慢模式”?

回答

  • 大量添加属性: 当命名属性数量超过一定阈值。
  • 使用 delete: 删除非最后添加的属性会破坏隐藏类的线性结构。
  • 属性名不固定: 频繁使用动态键名。 一旦进入慢模式(字典模式),属性访问将变慢,且难以恢复到快模式。

4. 如何优化对象属性的访问性能?

回答

  • 构造函数初始化: 在构造函数中一次性初始化所有属性。
  • 顺序一致: 保持不同实例的属性添加顺序一致,以复用隐藏类。
  • 避免 delete: 如果需要移除属性,建议将其设为 nullundefined
  • 使用数组存储索引数据: 尽量不要将索引作为普通对象的键,而是使用真正的数组。
最近更新