JavaScript 继承的6种方式复盘

340 阅读4分钟

前言

本篇谈一下关于JavaScript 继承相关的知识。包括继承的方式,已经一些优缺点等。本篇知识点获取来源红宝书。

继承方式

关于JavaScript 继承的方式,有大概的如下六种:

  • 原型链继承;
  • 借用构造函数继承;
  • 组合继承;
  • 原型式继承;
  • 寄生式继承;
  • 寄生组合式继承;

这里我们分别展开来讲解。

原型链继承

主要思想

利用原型让一个引用类型继承另一个引用类型的属性和方法。

实现代码:

function Parent () {
  this.name = 'cook';
}

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

function Child () {
  this.age = 1
}

const child = new Child();

console.log(child.getName()) // 'cook'

缺点:

  • 引用类型的原型属性会被所有实例共享;
  • 创建子类型的实例时(new Child()),不能向超类型的构造函数(Parent)中传递参数;

借用构造函数继承

借用构造函数也叫伪造对象经典继承

主要思想:

在子类型的构造函数的内部,调用超类型构造函数。

实现代码:

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

function Child (name) {
  // 继承了Parent,并传递了参数
  Parent.call(this, name)
}

const child1 = new Child('james');

child1.colors.push('pink');

console.log(child1.colors); // ['red', 'blue', 'green', 'pink']
console.log(child1.name); // 'james'

const child2 = new Child('henry');

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

优点:

  • 避免引用类型的原型属性会被所有实例共享;
  • 子类型的构造函数(Child)能向超类型构造函数(Parent)传递参数。

缺点:

  • 方法都在构造函数中定义,无法函数复用(无法继承超类型的原型Parent.prototype定义的方法);
  • 每次创建实例都会创建一遍方法(每new Child() 一次,就会执行 Parent.call(this, name));

组合继承

组合继承也叫伪经典继承。

主要思路:

使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。

实现代码:

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

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

function Child (name, age) {
  // 继承了Parent,并传递了参数
  // 通过借用构造函数来实现对实例属性的继承
  Parent.call(this, name)
  this.age = age;
}

// 使用原型链实现对原型属性和方法的继承
Child.prototype = new Parent();
Child.prototype.constructor = Child;

const child1 = new Child('james', 12);

child1.colors.push('pink');

console.log(child1.colors); // ['red', 'blue', 'green', 'pink']
console.log(child1.name); // 'james'
console.log(child1.age); // 12

const child2 = new Child('henry',15);

console.log(child2.colors); // ['red', 'blue', 'green']
console.log(child2.name); // 'henry'
console.log(child2.age); // 15

优点:

融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。

  • 避免引用类型的原型属性会被所有实例共享;
  • 子类型的构造函数(Child)能向超类型构造函数(Parent传递参数
  • 继承超类型的原型Parent.prototype定义的方法,实现函数复用

缺点:

  • 会调用两次父构造函数Parent

    • 一次是设置子类型实例的原型的时候(Child.prototype = new Parent());
    • 一次在创建子类型实例的时候;
    const child1 = new Child('james', 12); // 内部调用了Parent.call(this, name)
    
  • 存在实例属性过度赋值。使用 Child.prototype = new Parent() ,导致Child.prototypechild1 都拥有构造函数实例的属性。

原型式继承

主要思路:

借助原型,基于已有的对象创建新对象。简单来说就是Object.create 的模拟实现。

实现代码:

function create(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

缺点:

  • 引用类型的原型属性会被所有实例共享,这点跟原型链继承一样。

    const parent = {
      colors: ['red', 'blue', 'green'],
      age: 1,
    } 
    
    const child1 = create(parent);
    const child2 = create(parent);
    
    child1.age = 18;
    console.log(child2.age); // 1
    
    child1.colors.push('pink');
    console.log(child2.colors); // ['red', 'blue', 'green', 'pink']
    

寄生式继承

主要思路:

创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后返回对象。

实现代码:

function createAnother (o) {
    var clone = Object.create(o);
    clone.sayName = function () {
        console.log('hi');
    }
    return clone;
}

缺点:

跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

寄生组合式继承

主要思路:

通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

实现代码:

function create(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

function inheritPrototype(child, parent) {
    // 使用寄生式继承来继承超类型的原型
    const prototype = create(parent.prototype);
    prototype.constructor = child;
    // 将结果指定给子类型的原型
    child.prototype = prototype;
}

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

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

function Child (name, age) {
  // 通过借用构造函数来继承属性
  Parent.call(this, name)
  this.age = age;
}

// 使用的时候:
inheritPrototype(Child, Parent);

优点:

  • 只调用了一次构造函数,避免了在Child.prototype上创建不必要的、多余的属性;
  • 与此同时,原型链还能保持不变;
  • 还能够正常使用 instanceof 和 isPrototypeOf。