ES5 寄生组合式继承

手写 ES5 寄生组合式继承的实现

问题

手写 ES5 继承,使用寄生组合式继承方式实现。

解答

为什么是寄生组合式继承?

ES5 有多种继承方式,寄生组合式继承是最优解,因为它:

  • 只调用一次父类构造函数
  • 原型链保持完整
  • 能正常使用 instanceof

完整实现

// 父类
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}

Parent.prototype.sayName = function () {
  console.log(this.name);
};

// 子类
function Child(name, age) {
  // 继承实例属性(调用父类构造函数)
  Parent.call(this, name);
  this.age = age;
}

// 继承原型方法(核心代码)
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

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

// 测试
const child1 = new Child('Tom', 18);
const child2 = new Child('Jerry', 20);

child1.colors.push('green');
console.log(child1.colors); // ['red', 'blue', 'green']
console.log(child2.colors); // ['red', 'blue']

child1.sayName(); // Tom
child1.sayAge();  // 18

console.log(child1 instanceof Child);  // true
console.log(child1 instanceof Parent); // true

封装成工具函数

function inheritPrototype(Child, Parent) {
  // 创建父类原型的副本
  const prototype = Object.create(Parent.prototype);
  // 修复 constructor 指向
  prototype.constructor = Child;
  // 赋值给子类原型
  Child.prototype = prototype;
}

// 使用
function Parent(name) {
  this.name = name;
}

Parent.prototype.sayName = function () {
  console.log(this.name);
};

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

inheritPrototype(Child, Parent);

Child.prototype.sayAge = function () {
  console.log(this.age);
};

对比组合继承的问题

// 组合继承(有缺陷)
function Child(name, age) {
  Parent.call(this, name); // 第二次调用 Parent
  this.age = age;
}

Child.prototype = new Parent(); // 第一次调用 Parent(多余)
Child.prototype.constructor = Child;

组合继承调用了两次父类构造函数,且 Child.prototype 上会有多余的父类实例属性。

关键点

  • Parent.call(this) 继承实例属性,确保每个实例有独立的引用类型属性
  • Object.create(Parent.prototype) 创建原型副本,避免直接 new Parent() 带来的副作用
  • 必须修复 constructor 指向,否则 Child.prototype.constructor 会指向 Parent
  • 相比组合继承,父类构造函数只调用一次,效率更高
  • ES6 的 class extends 本质上就是寄生组合式继承的语法糖