继承的方式2:借助原型链(Prototype Chain Inheritance)
面试速答(30 秒版 TL;DR)
- 核心:让
Child.prototype成为Parent的一个实例(或等价的“以Parent.prototype为原型的对象”),从而让子类实例沿原型链访问父类原型方法。 - 经典写法(不推荐):
Child.prototype = new Parent()。 - 主要问题:无法给父构造函数传参(或很别扭);父构造函数执行一次会把引用类型属性挂到
Child.prototype上,导致所有子实例共享同一份引用类型属性。
图示:属性查找沿着原型链向上委托
经典实现(不推荐)与问题演示
function Parent() {
this.tags = []; // 引用类型
}
Parent.prototype.add = function (x) {
this.tags.push(x);
};
function Child() {}
Child.prototype = new Parent(); // Parent 构造函数执行了一次
Child.prototype.constructor = Child;
const c1 = new Child();
const c2 = new Child();
c1.add("a");
console.log(c1.tags); // ["a"]
console.log(c2.tags); // ["a"] 共享了同一个 tags(来自 Child.prototype)
为什么会共享:因为 tags 被创建在 Child.prototype 上,而所有实例读取/写入时都访问到同一个引用。
常见追问
Q1:那我把 tags 放到构造函数里不是就好了?
放构造函数里当然更合理,但这套“纯原型链继承”本身不调用父构造函数去初始化每个实例,所以你最终还是会走向“组合继承”(call + 原型链)。
Q2:只用原型链继承,有什么好处?
实现极其简单,并且能复用父类原型方法;但现实项目中通常要结合 call 才能同时解决“实例属性初始化”和“原型方法复用”。
易错点/坑
Child.prototype = new Parent()会执行父构造函数,可能带来副作用(发请求、注册事件、读环境等)。- 需要手动修正
constructor指回Child。
速记要点(可背诵)
- 纯原型链继承能拿到父类原型方法,但会引入“共享引用类型属性”和“父构造函数副作用”等问题。