__proto__访问绕过内联缓存因其是object.prototype上的访问器属性,每次调用需原型链检查、隐藏类校验及副作用检测,无法固化为内存偏移,故始终走慢速路径;应改用object.getprototypeof()或object.create()等更高效安全的方式。

__proto__ 属性的访问本身不被内联缓存(IC)优化,且属于高开销、应避免的运行时操作。
V8 等现代引擎对 __proto__ 的读写做了明确限制:它不是普通数据属性,而是对象原型链的动态代理入口,每次访问都要触发完整的原型链检查、隐藏类校验和潜在的副作用检测。这与 obj.prop 这类自有属性或稳定原型链上的方法调用有本质区别。
__proto__ 访问为什么绕过内联缓存?
-
__proto__是一个访问器属性(accessor property),定义在Object.prototype上,其get和set方法由引擎原生实现。 - IC 只对数据属性的读取路径做偏移量缓存;而访问器会强制跳转到函数调用逻辑,无法固化为内存偏移。
- 每次
obj.__proto__都需:- 检查对象是否禁用了原型访问(如
Object.freeze(obj)后的行为变化) - 确认当前
__proto__值是否被Object.setPrototypeOf或__proto__ = ...修改过 - 触发隐藏类失效检查(因
__proto__变更会重置整个对象的 Map)
- 检查对象是否禁用了原型访问(如
所以它天然无法进入单态 IC 路径,始终走慢速通用查找。
替代方案:用更高效、更安全的方式操作原型
✅ 读取原型:用
Object.getPrototypeOf(obj)
虽然也是访问器调用,但语义明确、不可被重写,部分引擎对其做了轻量优化(仍不如自有属性快,但比__proto__更可控)。✅ 设置原型(仅初始化时):用
Object.create(proto)或class继承
构造阶段一次性设定,之后不再修改,保证隐藏类稳定。-
❌ 避免这些写法:
obj.__proto__ = newProto-
obj.__proto__.method()(双重间接 + 访问器嵌套) - 在循环或高频函数中读写
__proto__
如果你真需要“快速原型方法调用”,这样做更有效
-
把常用方法提前绑定到实例上(尤其在构造函数中):
class Button { constructor() { // 提升为自有属性,IC 可直接命中 this.click = this.handleClick.bind(this); // 或直接赋值(无 this 依赖时) this.toString = Element.prototype.toString; } handleClick() { /* ... */ } } 对只读、无上下文依赖的方法,用
Object.assign(this, { method: Base.prototype.method })复制,让 IC 走最快的数据属性路径。
如何验证你没踩坑?
- 运行时加
node --trace-ic your.js,搜索含__proto__的行——如果持续显示megamorphic或根本没缓存记录,说明它完全没进 IC。 - 用
%DebugPrint(obj)查看对象 Map,若频繁变动或出现DICTIONARY_MODE,大概率是__proto__或动态增删导致的。
不复杂但容易忽略:__proto__ 不是性能友好的工具,它是调试和底层机制的接口,不是日常开发的属性访问手段。











