快属性指直接存放在对象实例内存块内的前10个常规字符串属性,访问只需一次内存偏移;慢属性则分两类:超出容量后存于外部properties数组(需指针跳转),或降级为字典模式(哈希查找,性能更差)。

“快属性”和“慢属性”不是语法概念,而是 V8 引擎内部对对象属性存储方式的优化策略。它们直接影响属性访问速度、内存布局,也间接放大或削弱继承链带来的开销。理解它们的关键,不在于“快慢”本身,而在于 V8 如何根据属性定义方式与继承结构,决定把属性放在哪儿、怎么查。
什么是“快属性”(In-object properties)
快属性指直接存放在对象实例内存块内部的属性。V8 为每个新对象预分配一小段固定空间(默认最多 10 个槽位),用于存放最早定义的常规字符串属性(如 obj.name = 'Alice')。这类属性访问只需一次内存偏移计算,无需额外指针跳转,所以极快。
- 只适用于数据属性(无 getter/setter)、且 descriptor 全默认(
writable: true,configurable: true,enumerable: true) - 一旦使用
Object.defineProperty添加带非默认 descriptor 的属性(哪怕只是configurable: false),后续所有属性都会被拒之门外,不再进入 in-object 区域 - 继承来的属性(来自 prototype)永远不算 in-object 属性——快属性只属于实例自身
“慢属性”包含两类:out-of-object 数组与字典模式
当对象超出 in-object 容量,或定义了非默认 descriptor、访问器属性时,V8 会把属性移到外部存储:
- Out-of-object(properties 数组):属性名和值以线性数组形式存在,通过隐藏类(Hidden Class)索引查找。仍属“快路径”,但比 in-object 多一次指针解引用
- Dictionary mode(字典模式):出现 getter/setter、频繁增删属性、或隐藏类变更过于频繁时触发。属性以哈希表形式存储,查找退化为 O(log n) 或更差,且无法被 JIT 优化器有效内联缓存
继承本身不直接导致字典模式,但若父类原型上大量使用 defineProperty 定义访问器,子类实例在访问这些继承属性时,可能因引擎判定“访问模式不可预测”而降级整个对象的属性存储策略。
继承如何放大“慢属性”的代价
原型链查找本身已是线性过程,而“慢属性”会让每一次查找雪上加霜:
- 访问自有 in-object 属性:1 次内存访问
- 访问自有 out-of-object 属性:1 次指针跳转 + 1 次数组索引
- 访问继承的 out-of-object 属性:先查自身(失败)→ 查
__proto__对象 → 再在其 properties 数组中索引 → 至少 2 次指针跳转 + 1 次索引 - 若父原型处于字典模式,每次访问都要哈希查找,且内联缓存(IC)极易失效——尤其当继承链上多个对象类型混杂时
例如:child.getName() 若 getName 在 3 层原型链外的 Base.prototype 上,且该 prototype 是字典模式,那么每次调用都绕过快速路径,性能损耗远超单次查找本身。
实际开发中可观察与规避的信号
不必手动检测隐藏类,但可通过行为判断是否已落入慢路径:
- 高频访问的属性(如动画帧中的
this.x)突然变卡,且该属性是后期用defineProperty添加的 - 子类实例创建后立即调用
Object.getOwnPropertyDescriptors(this),发现空对象——说明所有属性都被推到了 out-of-object 或字典中 - 用 Chrome DevTools 的
%DebugPrint(obj)查看对象结构,若显示DICTIONARY_ELEMENTS或DICTIONARY_PROPERTIES,即已降级 - 避免在构造函数外反复增删属性;继承方法优先放 prototype,而非在实例上重新定义
不复杂但容易忽略:保持属性定义顺序一致、避免运行时修改 descriptor、慎用访问器替代简单字段——这些小习惯,就是在为继承链上的每一个实例守住“快属性”的入口。











