人人都会AI编程

4.3 原型链的完整查找机制

更新时间:2026-07-11

上两节理清了 prototypeproto 的区别和联系,现在是时候把它们串成一条完整的“属性查找流水线”了。原型链的查找机制用一句话概括就是:就近原则,逐级上溯,碰到 null 结束

一条链路,四个参与者

假设我们有以下代码:

function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  console.log('Hi, I am ' + this.name);
};

const p1 = new Person('Alice');

此时 p1 的原型链可以这样画出:

p1 → p1.__proto__ → Person.prototype → Object.prototype → null

当访问 p1.name 时:

  1. 引擎首先在 p1 自身属性中查找,发现有 name,直接返回 'Alice'

当访问 p1.sayHi 时:

  1. p1 自身属性中查找,没有找到 sayHi
  2. 沿着 p1.proto 进入 Person.prototype,在它的属性中查找,找到 sayHi 方法,返回。
  3. 查找停止,不再继续向上。

当访问 p1.toString 时:

  1. p1 自身没有 toString
  2. Person.prototype 也没有 toString
  3. 继续向上,进入 Person.prototype.proto,也就是 Object.prototype,在这里找到了 toString 方法,返回。

当访问一个完全不存在的属性 p1.unknown 时:

  1. 引擎逐级向上查找,直至到达 Object.prototype
  2. Object.prototype.protonull,查找结束,返回 undefined,不会报错。

这个机制带来的实用启示

1. 属性屏蔽(遮蔽)
如果实例自身有一个与原型链上同名的属性,那么访问时一定会命中自身的,原型链上的同名属性暂时“隐身”,这就是属性屏蔽。调试时可以通过 hasOwnProperty 方法确认某个属性到底属于哪个层级。

2. 修改原型影响所有实例
因为每次访问都是“运行时”查找,如果在运行时向 Person.prototype 上添加一个新方法,那么所有已经创建的 Person 实例都会立即能使用这个方法。这种动态性是原型模型的强大之处,但也需要谨慎使用,避免造成不可预期的行为。

3. 过长的原型链会拖慢查找
虽然现代引擎对原型链查找做了大量优化(如内联缓存),理论上每次属性访问都要逐级遍历,深层嵌套仍然会带来可感知的开销。在设计对象继承关系时,尽量保持链路扁平,不要人为制造过深的层次。

4. Object.create(null) 的妙用
如果完全不希望一个对象有原型链(比如用作纯数据字典),可以用 Object.create(null) 创建。这样的对象连 toStringhasOwnProperty 都没有,查找时完全不会遍历到 Object.prototype,既干净又略有性能优势。

原型链查找机制是整个 JavaScript 对象模型最核心的运转规则。当你理解了“从哪儿开始找、按什么顺序找、什么时候停”,那么构造函数、继承、instanceof 运算符等等背后的行为,都会变得透明而自然。