一文学会JS的原型继承

96 阅读3分钟

JavaScript 中的原型继承有以下几种方式:

  1. 原型链继承:通过将子类的原型设置为父类的实例对象来实现继承。缺点是父类的引用类型属性会被所有子类实例共享。
  2. 构造函数继承(借助构造函数):利用父类的构造函数来增强子类实例,即在子类构造函数中调用父类构造函数。缺点是无法继承父类的原型上的属性和方法。
  3. 组合继承(融合原型链继承和借助构造函数继承):使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。缺点是会调用两次父类构造函数,导致效率低下。
  4. 原型式继承:用一个临时构造函数作为中介,将某个对象直接赋值给临时构造函数的原型,然后返回这个临时构造函数的一个实例对象。缺点是如果没有正确处理好引用类型的属性,可能会导致它们之间相互影响。
  5. 寄生式继承:创建一个实现继承的函数,以某个对象为基础,并进行增强,最后返回这个对象。可用于给对象添加一些额外的属性或方法,但也可能会像原型式继承一样导致引用类型属性的共享。
  6. 寄生组合式继承(最优解):通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。既不会调用两次父类构造函数,也不会让实例与原型之间多余的属性和方法。

举一个简单的例子来说明这几种继承方式。

function Animal(name) {
  this.name = name;
}

Animal.prototype.say = function() {
  console.log(`I am ${this.name}.`);
}

Animal.prototype.eat = function() {
  console.log(`${this.name} is eating.`);
}

现在我们想创建一个 Cat 类来继承 Animal,以下是各种继承方式的实现:

  1. 原型链继承:
function Cat(name, color) {
  Animal.call(this, name);
  this.color = color;
}

Cat.prototype = new Animal();

const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."
  1. 构造函数继承(借助构造函数):
function Cat(name, color) {
  Animal.call(this, name);
  this.color = color;
}
// 将子类的原型指向父类的原型实现对父类原型属性和方法的继承 
Cat.prototype = Object.create(Animal.prototype); 
Cat.prototype.constructor = Cat;

const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
// undefined
  1. 组合继承(融合原型链继承和借助构造函数继承):
function Cat(name, color) {
  Animal.call(this, name);
  this.color = color;
}

Cat.prototype = new Animal();

const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."

  1. 原型式继承:
function createObj(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

const cat = createObj(Animal.prototype);
cat.say(); // output: undefined
  1. 寄生式继承:
function createCat(obj) {
  const cat = Object.create(obj);
  cat.color = 'white';
  cat.meow = function() {
    console.log('Meow~');
  }
  return cat;
}

const cat = createCat(new Animal('Kitty'));
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."
cat.meow(); // output: "Meow~"

  1. 寄生组合式继承:
function Cat(name, color) {
  Animal.call(this, name);
  this.color = color;
}

function inheritPrototype(subType, superType) {
  const prototype = Object.create(superType.prototype);
  prototype.constructor = subType;
  subType.prototype = prototype;
}

inheritPrototype(Cat, Animal);

const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."

ES6 的 Class 继承其实使用的是组合继承,即通过 extends 关键字实现原型链继承,通过 super() 方法调用父类的构造函数实现借助构造函数继承。这种方式与传统的组合继承相似,但比传统的组合继承更简单和直观。

class Animal {
  constructor(name) {
    this.name = name;
  }

  say() {
    console.log(`I am ${this.name}.`);
  }

  eat() {
    console.log(`${this.name} is eating.`);
  }
}

class Cat extends Animal {
  constructor(name, color) {
    super(name);
    this.color = color;
  }
}

const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."