03原型与继承

JS 没有传统 class 的「复制式继承」,它靠原型链(Prototype Chain)共享方法。 搞懂 __proto__ 与 prototype 的区别、new 到底做了什么、 class 只是语法糖——你就打通了 JS 对象模型的任督二脉。

约 16 分钟 4 个动手实验 JS 底层机制

一、对象的「隐藏指针」:__proto__

每个对象都有一个内部槽 [[Prototype]](浏览器暴露为 __proto__), 指向另一个对象。属性查找时,自己没有就顺着这条链往上找。

实例 obj 自己的属性:name = '小明'
↑ __proto__ 找不到 name 就往上
原型 Person.prototype 共享方法:sayHi() { … }
↑ 再往上
Object.prototype toString / hasOwnProperty / valueOf …
↑ 最顶上是 null
属性查找沿原型链:自己没有 sayHi,却能调用——方法在原型上
__proto__ 是历史遗留,面试爱问,生产代码用 Object.getPrototypeOf(obj) / Object.setPrototypeOf() 更规范。

二、prototype vs __proto__:别再搞混

prototype__proto__
谁有 只有函数有 所有对象(含函数)都有
是什么 将来 new 出来的实例的原型 自己指向的原型对象
关系 instance.__proto__ === Fn.prototype

三、new 的四个步骤

写 const p = new Person('小明') 时,引擎做了四件事:

  1. 创建一个空对象 {}
  2. 把这个对象的 __proto__ 指向 Person.prototype
  3. 以该对象为 this 执行构造函数(挂上 name 等属性)
  4. 如果构造函数没返回对象,就返回这个新对象
手写一个 miniNew:和真 new 对比结果,看四步在代码里长什么样

四、class:原型的语法糖

ES6 的 class 底层还是原型链,但写起来像传统 OOP。 constructor、方法、extends、super 都会映射到原型操作。

class 继承实战:Animal → Dog,看方法覆盖、super 调用、instanceof 链

五、继承的三种写法演进

时代写法评价
ES5 原型 Child.prototype = Object.create(Parent.prototype) 底层原理,面试要懂
ES5 寄生组合 借用构造 + 原型桥接 最完美的 ES5 方案
ES6 class class Child extends Parent 项目首选,语法清晰
真实项目直接写 class / 组合优于继承。 原型链是「知其所以然」——排查 instanceof、方法找不到、框架源码时用得上。

六、几个高频判断 API

API作用注意
obj instanceof Fn obj 的原型链上是否有 Fn.prototype 跨 iframe 会误判
obj.hasOwnProperty(k) 属性是否「自己的」(不在原型上) for…in 会遍历原型可枚举属性
Object.getPrototypeOf(o) 安全读原型 替代 __proto__
Object.create(proto) 指定原型创建对象 实现继承的基石
'k' in obj 自己或原型上是否存在 比 hasOwnProperty 范围大
instanceof / hasOwnProperty / in 的差异:同一个属性三种判断结果可能不同

七、组合优于继承

深继承链(A→B→C→D)会让代码变得僵硬。现代实践更推荐 组合(Composition):把能力做成小模块,按需拼装。

八、本章小结

  • 属性查找沿原型链向上;自己没有就找 __proto__。
  • prototype 是函数才有(给 new 出的实例准备的原型); __proto__ 是每个对象的原型指针。
  • new 四步:建对象 → 接原型 → 执行构造 → 返回对象。
  • class / extends 是原型的语法糖,项目首选。
  • hasOwnProperty 只认自己的;in / instanceof 会看原型链。
  • 深层继承僵硬时,优先考虑组合。
动手练习: ① 手写 Object.create 的简化版(指定原型创建对象); ② 用 class 写一个 Shape 基类和 Circle 子类,子类覆盖面积方法并用 super 复用描述方法; ③ 画出 const d = new Dog() 的原型链关系图。