继承的方式3:把 call 与原型链组合(组合继承)
面试速答(30 秒版 TL;DR)
- 核心:子构造函数里
Parent.call(this, ...)继承实例属性;同时Child.prototype连接到Parent.prototype继承原型方法。 - 经典写法(会有性能/副作用问题):
Child.prototype = new Parent()。 - 主要缺点:父构造函数会执行两次:
- 一次在
Parent.call(this)(初始化每个实例) - 一次在
Child.prototype = new Parent()(为建立原型链)
- 一次在
最小可运行示例(经典组合继承)
function Parent(name) {
this.name = name;
this.tags = [];
}
Parent.prototype.say = function () {
return `I am ${this.name}`;
};
function Child(name) {
Parent.call(this, name); // 1) 继承实例属性
}
Child.prototype = new Parent("prototype-seed"); // 2) 继承原型方法(但会额外执行一次 Parent)
Child.prototype.constructor = Child;
const c = new Child("alice");
console.log(c.say()); // "I am alice"
为什么叫“组合”?
call解决:实例字段初始化、可传参、避免共享引用类型- 原型链解决:方法复用(放在
Parent.prototype上的函数只创建一份)
常见追问
Q1:父构造函数执行两次有什么实际问题?
- 性能:多一次不必要的初始化。
- 副作用:如果
Parent构造函数里有副作用逻辑,会被多执行一次。 - 语义:你需要传一个“种子参数”(上面
"prototype-seed")去构造Child.prototype,这本身不自然。
Q2:怎么优化掉第二次 new Parent()?
有两条路:
- 优化1:让
Child.prototype = Parent.prototype(会有共享原型对象的大坑) - 优化2(更推荐):
Child.prototype = Object.create(Parent.prototype)(寄生组合继承)
下面两篇分别讲。
速记要点(可背诵)
- 组合继承 =
Parent.call(this)+Child.prototype连到Parent.prototype。 - 经典写法的核心问题是:
Parent构造函数执行了两次。