章节摘要:对象不是属性的口袋,而是一串节点——自己的属性放本体,公共能力放在原型上,原型又有原型,直到 null。本章逐帧回放一次属性访问的完整查找路径,再看引擎为加速这条路发明的两件工具:隐藏类(给对象定型)与内联缓存(记住上次在哪找到的),最后用同一种视角审视数组的「空洞」与元素类型,解释为什么往数字数组里塞一个字符串会让性能滑坡。读完本章,继承、 instanceof、性能形状稳定三件事会在同一张图里合流。
阅读完本章,你应当能够:
obj.method() 的完整查找序列:自身属性 → 原型 → 原型的原型 → null;__proto__、prototype、constructor 三个容易混淆的名词各自指向什么;属性访问是一次向上攀爬:每一站先翻自己的口袋,翻不到就去上一站。class 语法、extends、instanceof 全部建立在这条攀爬路线上。
把 student.fullName 的一次访问拆成一帧帧:查本体、查 Student.prototype、查 Object.prototype、到 null 判 undefined 或 TypeError。同时理清 new 的四步动作与 instanceof 的实现原理,你会发现 class 不过是这条链的糖衣。
引擎给「形状相同」的对象共享一张隐藏类,属性访问被缓存成「按偏移量直取」。对象形状乱改会导致隐藏类转移链分叉、内联缓存失效、优化退场。这一节把引擎的加速器讲透,也给出保持形状稳定的写法清单。
数组在引擎眼里是一块带类型的连续存储:全是小整数是一种优化形态,混入字符串就降级,挖出空洞更特殊。map、filter、reduce 的语义与代价、类数组与真数组的转换,都从「存储形态」这个视角重新讲一遍。
三节共享同一件事的三个层面:3.1 是「查找的正确性」(沿链上溯的语义),3.2 是「查找的速度」(缓存与形状),3.3 是「查找的存储底座」(元素类型与空洞)。
一次 obj.x 访问 | +-- 语义层:3.1 沿原型链上溯,直到命中或到顶 | +-- 速度层:3.2 隐藏类定型 + 内联缓存记住上次的站 | +-- 存储层:3.3 数组场景下 x 是下标,直接映射到元素存储
第 1 章流水线里的反馈向量在此落地为内联缓存;本章的形状稳定话题是第 6 章性能优化的直接前置;错误查找路径(查到 null 抛 TypeError)是第 7 章「报错解读」的素材来源。
问:都 2026 年了,还要学原型链吗?class 不是已经替代它了?
class 只是糖衣,链没有变——你在控制台打印任何 class 实例的原型,看到的仍是构造器的 prototype 对象。读懂原型链的理由有三个:存量代码里全是函数构造器风格;instanceof、Object.create、原型污染防护等机制仍直接操作这条链;面试与源码阅读绕不开。会用 class 写代码与理解代码怎么跑,是两层能力。
问:隐藏类是 V8 独有的吗?
概念上各家引擎都有对应机制(SpiderMonkey 的 shape、JavaScriptCore 的 structure),名字不同、思路一致:给动态对象建形状索引。所以「保持形状稳定」这条建议跨引擎通用,不是某个实现的偏好。
问:性能相关的两节(3.2、3.3)要背结论吗?
不背,记判定方法:形状是否稳定、元素是否纯净、扫描了几遍。具体数值在不同机器与引擎版本上浮动很大,但判定方法稳定。第 6 章会把这套判定方法扩展成完整清单。
问:原型与 class 的学习精力怎么分配?
写代码用 class,读代码要懂原型——两条线并行不冲突。本章把篇幅放在原型链上,因为 class 的语法表层(构造器、方法、继承关键字)在写的过程中自然就会了,而链的语义只靠写是碰不到的。读完 3.1 你应该能把任何 class 代码在脑内翻译成原型语言,这个能力在读框架源码时会持续回报。性能两节(3.2、3.3)则负责把「查找」从语义正确推进到代价可控。
自测三题:其一,写出 student.toString() 的完整查找帧序列,直到 Object.prototype;其二,解释「先加 y 再加 x」与「先加 x 再加 y」的对象为什么在引擎眼里是两种形状,后果是什么;其三,一段「数字数组里混入一个字符串再排序」的代码,说出它降级到了哪种元素形态、大概慢多少。答得出这三题,本章的语义层、速度层、存储层就都通了。