ES6 之前 JS 如何实现继承

ES6 之前的继承是通过原型来实现的,也就是每一个构造函数都会有一个 prototype 属性,调用实例的方法或属性时,首先在自身上寻找,再去构造函数的 prototype 上寻找;prototype 本质上也是一个对象实例,如果还没找到,会继续沿原型链上溯到 Object.prototype,最终到 null。

实现继承的核心就是让子类的原型指向父类的一个实例,从而形成原型链。

寄生组合式继承(最常用方案)

function Parent(name) {
  this.name = name;
}
Parent.prototype.say = function () {
  console.log('hi, ' + this.name);
};

function Child(name, age) {
  Parent.call(this, name);   // 1. 继承父类实例属性
  this.age = age;
}

// 2. 继承父类原型方法(关键:用 Object.create,避免调用 Parent 构造函数)
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;  // 修正 constructor 指向

const c = new Child('Tom', 18);
c.say(); // hi, Tom

补充:原型链继承的几种典型写法与对比:

  • 原型链继承:直接把 Child.prototype = new Parent(),问题:父类引用类型属性被所有子类共享,且 Parent 被调用了一次无法传参。
  • 构造函数继承:在子类构造里 Parent.call(this, ...),问题:方法都建在实例上,无法复用。
  • 组合继承:上面两者结合,但 Parent 仍会被调用两次(一次在原型上、一次在构造里)。
  • 寄生组合式继承:通过 Object.create(Parent.prototype) 拿一个干净的父类原型副本,避免 Parent 多余调用,是目前最合理的方式,也是 ES6 class 的底层实现思路。
  • ES6 class extends:本质上是寄生组合式继承的语法糖,强烈推荐。

来源整理自:vue3js.cn 面试官系列