JS组合式继承中父构造函数执行两次的原因及解决方案

ChatGPT4o 使用 ChatGPT 4o 回答

问题:

JS 的组合式继承 为什么父构造函数会执行两次

回答:

组合式继承(也称为伪经典继承)是 JavaScript 中一种常见的继承方式,它结合了原型链继承和构造函数继承的优点。组合式继承的主要步骤包括:

  1. 使用原型链继承父类的原型属性和方法。
  2. 在子类构造函数中调用父类构造函数,以继承父类的实例属性。

然而,这种继承方式有一个问题:父类的构造函数会被调用两次。让我们通过一个示例来详细解释这个问题。

// 父类构造函数
function Parent(name) {
    this.name = name;
    console.log('Parent constructor called');
}

// 父类原型方法
Parent.prototype.sayName = function() {
    console.log(this.name);
};

// 子类构造函数
function Child(name, age) {
    // 第一次调用父类构造函数
    Parent.call(this, name);
    this.age = age;
}

// 子类原型继承父类原型
Child.prototype = new Parent(); // 第二次调用父类构造函数
Child.prototype.constructor = Child;

// 子类原型方法
Child.prototype.sayAge = function() {
    console.log(this.age);
};

// 测试
const child = new Child('John', 25);
child.sayName(); // John
child.sayAge();  // 25

在上面的代码中,父类构造函数 Parent 被调用了两次:

  1. 第一次调用是在 Child 构造函数内部,通过 Parent.call(this, name) 来调用父类构造函数。这是为了确保子类实例能够继承父类的实例属性。
  2. 第二次调用是在 Child.prototype = new Parent(); 这行代码中。这里通过创建一个新的 Parent 实例来设置子类的原型,这样子类就能继承父类的原型方法。

这种方式虽然实现了继承,但也带来了性能上的开销,因为父类构造函数被调用了两次。

解决方案

为了避免父类构造函数被调用两次,可以使用寄生组合式继承。这种方式通过创建一个中间对象来继承父类的原型,���不是直接调用父类构造函数。

// 父类构造函数
function Parent(name) {
    this.name = name;
    console.log('Parent constructor called');
}

// 父类原型方法
Parent.prototype.sayName = function() {
    console.log(this.name);
};

// 子类构造函数
function Child(name, age) {
    Parent.call(this, name); // 只调用一次父类构造函数
    this.age = age;
}

// 创建一个中间对象来继承父类的原型
function inheritPrototype(child, parent) {
    const prototype = Object.create(parent.prototype); // 创建父类原型的一个副本
    prototype.constructor = child; // 修正子类构造函数的指向
    child.prototype = prototype; // 将子类的原型指向这个副本
}

// 使用寄生组合式继承
inheritPrototype(Child, Parent);

// 子类原型方法
Child.prototype.sayAge = function() {
    console.log(this.age);
};

// 测试
const child = new Child('John', 25);
child.sayName(); // John
child.sayAge();  // 25

在这个改进的版本中,父类构造函数只被调用了一次,从而避免了不必要的性能