准确地说,JS 中没有 的概念,有的只是 构造函数原型链。不过,我们可以通过构造函数和原型链来实现类似于类的继承,并且可以玩出很多花样、实现不同的特性。

# 原型链继承

直接把要继承的构造函数的 prototype 指向一个实例化的父类对象即可。

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  console.log(this.name + " makes a noise.");
};
function Dog(name, breed) {
  this.breed = breed;
}
Dog.prototype = new Animal(); // 实现继承
let dog = new Dog("Rex", "Bulldog");
dog.speak(); // Output: Rex makes a noise.

优点:简单易懂,符合原型链的基本思想

缺点:

  • 如果弗雷包含引用类型的属性(如数组),子类实例对这个属性的修改影响其他子类实例, 因为是以引用的方式继承的
  • 在创建子类实例时,不能向父类构造函数传递参数。

# 构造函数继承

直接调用父类构造函数的 Function.prototype.call 方法,指定 this 和参数即可。

function Animal(name) {
  this.name = name;
}
function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}
let dog = new Dog("Rex", "Bulldog");

优点:

  • 避免了引用类型的属性共享问题,传入的值直接在这个实例中单独初始化。
  • 可以在创建子类实例时向父类传递参数。

缺点:

  • 方法都在构造函数中定义,无法直接的实现函数复用,每个实例都有自己独立的方法副本。

# 组合继承(原型链继承 + 构造函数继承)

在子类构造函数内部先用 call 绑定 this ,然后再指定子类构造函数的 prototype 为父类的实例,最后把 prototype.constructor 指向自己。

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  console.log(this.name + " makes a noise.");
};
function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
let dog = new Dog("Rex", "Bulldog");

优点:结合了原型链继承和构造函数继承的优点,避免了它们各自的缺点。

缺点:调用了 两次 父类构造函数,导致子类原型上 多了不需要的父类属性

# 原型式继承

使用 Object.create 方法来创建一个子类对象。

let animal = {
  type: "animal",
  speak: function () {
    console.log(this.name + " makes a noise.");
  },
};
let dog = Object.create(animal);
dog.name = "Rex";

优点:不需要定义构造函数,只需要指定要继承的对象,就可以创建对象。

缺点:和原型链继承一样,引用类型的属性会被共享。

# 寄生式继承

提供了一个返回子类实例的函数,在其内部调用 Object.create 方法创建子类实例,并为其指定相关属性,之后将其返回。

function createDog(name) {
  let dog = Object.create(animal);
  dog.name = name;
  return dog;
}

优点:可以在不必为新对象构建自定义类型的情况下,实现对象的属性继承。

缺点:和原型链继承一样,引用类型的属性会被共享。

# 寄生组合式继承

引入一个函数 inheritPrototype ,传入子类构造函数和父类构造函数,将两者的 prototype 绑定到一起(类似于 new 操作符)。

其余的部分和构造函数继承类似。

function inheritPrototype(subType, superType) {
  let prototype = Object.create(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  console.log(this.name + " makes a noise.");
};
function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}
inheritPrototype(Dog, Animal);
let dog = new Dog("Rex", "Bulldog");
dog.speak();

优点:解决了组合继承中调用两次父类构造函数的问题,避免了不必要的属性初始化。

缺点:相对于其他方式,寄生组合式继承是比较完善的一种继承方式,但是在方法实现上相对比较复杂。