← 返回题目列表

什么是原型和原型链?

高频 中等 第 9 / 29 题 更新于 2026/07/27
JavaScript原型原型链继承

简化版

每个对象都有内部原型,访问属性时如果对象自身没有,就会沿着原型链向上查找。函数有 prototype 属性,用于给通过 new 创建的实例提供共享方法。

详细版

核心关系:

function Person(name) {
  this.name = name;
}
Person.prototype.say = function () {
  return this.name;
};

const p = new Person('Tom');
p.say();

p 自身没有 say,会沿着 p.__proto__ 找到 Person.prototype。继续往上是 Object.prototype,再往上是 null

原型链的价值是方法共享:实例属性放对象自身,公共方法放原型上,避免每个实例都复制一份函数。

面试要注意:__proto__ 是对象的内部原型访问器,prototype 是函数对象上的属性,两者不是一回事。

完整版教学

一、属性查找过程

当执行 p.say 时,引擎先查 p 自己有没有 say。没有就查 p 的原型,再查原型的原型,直到找到属性或到达 null

这条查找路径就是原型链。找到访问器属性时,getter 中的 this 通常仍是最初接收者 p,而不是定义 getter 的原型对象,这说明属性查找位置与调用接收者是两个维度。

二、new 做了什么

new Person('Tom') 大致做四件事:

  1. 创建一个新对象。
  2. 把新对象的原型指向 Person.prototype
  3. 用新对象作为 this 执行构造函数。
  4. 如果构造函数没有返回对象,就返回这个新对象。

这解释了为什么实例能访问 Person.prototype 上的方法。

三、prototype 和 proto 的区别

prototype 是普通可构造函数默认拥有的属性,主要服务于构造函数创建实例;箭头函数等不可构造函数没有这种默认属性,普通对象也可以自行定义同名业务属性。

__proto__ 是对象访问内部原型的历史访问器。现代代码更推荐:

Object.getPrototypeOf(obj);
Object.setPrototypeOf(obj, proto);

四、原型链的风险

原型链太深会影响属性查找效率,也会让代码难维护。修改内置对象原型也很危险:

Array.prototype.foo = function () {};

这可能污染全局行为,影响第三方库和遍历逻辑。业务开发通常避免修改内置原型。

五、面试追问与工程落地

原型链题常会追问“属性遮蔽”。如果实例自身有 say,就不会继续去原型上找同名 say。这叫 shadowing。判断属性是否来自自身可以用 Object.hasOwn(obj, key)hasOwnProperty,不要把“能访问到”误认为“一定是自身属性”。

还可能追问 constructor。默认情况下,Person.prototype.constructor === Person,但如果你整体替换了 prototype,constructor 可能丢失,需要手动修正。现代 class 语法减少了这类手写错误,但读老代码时仍然很重要。

工程里不要为了继承滥用深原型链。业务对象组合通常比继承更容易维护。原型链最有价值的场景是理解 JS 对象模型、class 本质、方法共享,以及排查属性查找和 monkey patch 带来的问题。

六、用对象关系图推导读取与写入

创建两个实例时,实例各自保存 name,但共享同一个 Person.prototype.say 函数。若每个函数对象粗略占 1KB,创建 1000 个实例时,把方法放构造器内部可能创建约 1000 份函数;放原型上只创建 1 份。这个数字只是帮助理解共享机制,真实内存取决于引擎优化,不能当性能基准。

p ──[[Prototype]]──> Person.prototype ──[[Prototype]]──> Object.prototype ──> null
│                         │
└─ name: 'Tom'            └─ say: function
表达式检查或改变什么结果含义
'say' in p自身加整条原型链能访问到即为 true
Object.hasOwn(p, 'say')仅自身属性本例为 false
Object.getPrototypeOf(p)内部 [[Prototype]]本例为 Person.prototype
p instanceof Person默认检查 prototype 是否在链上本例为 true
Object.create(proto)以给定对象建立新对象原型不执行构造函数

读取继承数据属性时会沿链返回值;给实例的同名可写数据属性赋值,通常在实例自身创建遮蔽属性,而不是修改原型。但若原型属性是 setter、不可写数据属性或对象使用 Proxy,写入行为会不同,所以“赋值永远写自身”也只是普通场景简化。

prototype 是构造函数用于实例连接的普通属性,[[Prototype]] 是每个对象参与继承查找的内部槽;两者通过 new 建立关系,但不是同一个概念。

七、常见误区与追问

  • 误区:所有对象都有可用的 .prototype 属性。 普通实例参与原型链却通常没有自身 prototype 属性;该属性主要存在于可构造函数上。
  • 误区:obj.__proto__ 就是规范内部槽本身。 它是历史兼容访问器,业务代码应优先使用 Object.getPrototypeOf 等标准方法。
  • 误区:给继承属性赋值一定会修改原型。 普通可写数据属性通常会在接收者自身创建同名属性,从而遮蔽原型值。
  • 追问:new 遇到构造函数显式返回值会怎样? 返回对象时采用该对象;返回原始值时通常忽略它并返回新创建的实例。
  • 追问:替换 Person.prototype 会影响旧实例吗? 旧实例仍连接旧原型对象,新实例连接新对象;替换变量属性不会自动重连已有实例。
  • 追问:instanceof 能否被自定义? 可以,右侧对象的 Symbol.hasInstance 方法优先参与判断,因此它不总是单纯原型链检查。
  • 追问:为什么不建议频繁 Object.setPrototypeOf 动态改变原型会破坏引擎对对象形状与属性查找的优化假设,也会增加维护复杂度。

八、加强记忆

原型链可以记成“自己没有就问上级”。实例通过内部原型连到构造函数的 prototype,公共方法放原型上复用;prototype 属于函数,原型链属于对象属性查找机制。