跳到主要内容

继承的方式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 构造函数执行了两次。