object.getprototypeof 极快是因为直接读取隐藏类中固化不变的prototype指针,单条机器指令完成;原型修改会破坏内联缓存与隐藏类稳定性,导致退化为慢路径。

Object.getPrototypeOf 之所以快,不是因为它“查得快”,而是因为它根本不需要查——它直接返回对象隐藏类(Map)中早已固化、不可变的原型指针。这种极速寻址的本质,源于 V8 对原型链结构的静态建模与零开销绑定,而非运行时遍历或哈希查找。
隐藏类里早就存好了 prototype 指针
每个隐藏类(Map)在创建时就明确记录了其对应的 prototype 值——这个值来自构造函数的 constructor.prototype 或字面量的默认原型(如 Object.prototype)。
- 对象一旦被赋予某个隐藏类,它的原型就永久锁定在这个 Map 上;
-
Object.getPrototypeOf(obj)不访问对象自身内存,也不遍历原型链,只是读取该对象所关联隐藏类中的一个固定偏移字段(通常是 Map 结构体内的第 2 或第 3 个字字段); - 这个读取等同于 C++ 中
obj->map()->prototype(),单条机器指令即可完成。
隐藏类平滑演进不扰动 prototype 字段
隐藏类迁移链(如 C0 → C1 → C2)只改变属性布局信息(新增字段名、偏移量、描述符),从不修改 prototype 字段:
- 即使你反复添加属性
obj.a = 1; obj.b = 2; obj.c = 3,新生成的 C1、C2、C3 都继承并复用原始隐藏类的 prototype 值; - delete 属性或
Object.setPrototypeOf才会切断该链——但那是主动破坏,不是演进本身的行为; - 所以只要没改原型,所有迁移后的隐藏类仍指向同一个 prototype,
getPrototypeOf始终返回同一地址,无需重新解析。
为什么 Object.setPrototypeOf 会让 getPrototypeOf 变慢?
它不慢在“读”,而在于后续所有 getPrototypeOf 调用都失去内联缓存(IC)保障:
- 修改原型后,V8 废弃原隐藏类,对象进入字典模式或新 Map,且该 Map 的 prototype 字段可能被标记为“不稳定”;
- JIT 编译器无法再对
getPrototypeOf(obj)做单指令硬编码,必须退回到通用函数调用路径; - 更严重的是,若该对象曾被热函数内联访问过原型方法(如
obj.toString()),整个函数会被去优化,下次执行降级为解释模式。
如何验证 getPrototypeOf 是否走极速路径?
在启用 --allow-natives-syntax 的环境下(如 d8 或 Node.js):
- 执行
%DebugPrint(obj),观察输出中map:后地址,再执行%DebugPrint(obj.__proto__),两者地址应严格一致(即 prototype 是 Map 的一部分,非动态计算); - 若
obj经Object.setPrototypeOf(obj, null),则%DebugPrint(obj)中 map 类型会变为MAP_KIND_PROXY或MAP_KIND_OTHER,且prototype字段显示为null—— 此时getPrototypeOf仍快,但已脱离原优化轨道; - 使用
console.log(%HasFastProperties(obj))为true时,基本可确认getPrototypeOf处于极速路径。
不复杂但容易忽略











