002 图解继承

8/13/2021 Javascript

本文讲解JavaScript各种继承方式和优缺点

# 原型继承

  1. 原型链继承

      function Parent() {
          this.name = ["Allen", "Diaz"];
      }
      Parent.prototype.getNmae = function () {
          console.log(this.name);
      };
    
      function Child() {}
      Child.prototype = new Parent();
      const instanceA = new Child();
      instanceA.name.push('Bob');
      const instanceB = new Child();
      console.log(instanceA.name); // ["Allen", "Diaz", "Bob"]
      console.log(instanceB.name); // ["Allen", "Diaz", "Bob"]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    缺点:

    • 引用类型的name属性被实例A instanceA 与 B instanceB共享导致 instanceB。name 也被修改
    • 在实例化时,无法向 Parent 传值
    foo
  2. 经典继承(利用构造函数)

      function Parent() {
          this.name = ["Allen", "Diaz"];
      }
    
      function Child() {
          Parent.call(this)
      }
    
      const instanceA = new Child();
      instanceA.name.push("Bob");
    
      const instanceB = new Child();
    
      console.log(instanceA.name); // ["Allen", "Diaz", "Bob"]
      console.log(instanceB.name); // ["Allen", "Diaz"]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    foo

    优点

    • .避免了引用类型的属性被所有实例共享
    • 可以在 Child 中向 Parent 传参

    缺点

    • 方法都在构造函数中定义,每次创建实例都会创建一遍方法
  3. 组合继承

    将上述两者进行结合

      function Parent(val) {
          this.value = val
          this.name = ["Allen", "Diaz"];
      }
      Parent.prototype.getName = function () {
          console.log(this.name)
      }
    
      function Child(age, value) {
          Parent.call(this, value)
          this.age = age
      }
    
      Child.prototype = new Parent()
    
      const instanceA = new Child(18, 'testAA')
      instanceA.name.push('Bob')
    
      const instanceB = new Child(19, 'testBB')
    
      console.log(instanceA)
      console.log(instanceB)
      instanceA.getName(); // ["Allen", "Diaz", "Bob"]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    foo

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

  4. 寄生组合式继承

    组合继承最大的缺点是会调用两次父构造函数

    一次是设置子类型实例的原型的时候:Child.prototype = new Parent();

    一次在创建子类型实例的时候:var child1 = new Child('kevin', '18');

    那么我们该如何精益求精,避免这一次重复调用呢?

    如果我们不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?

      function Parent(name) {
          this.name = name;
          this.colors = ['red', 'blue', 'green'];
      }
    
      Parent.prototype.getName = function () {
          console.log(this.name)
      }
    
      function Child(name, age) {
          Parent.call(this, name);
          this.age = age;
      }
    
      // Object.create 的模拟实现
      var F = function () {};
      F.prototype = Parent.prototype;
      Child.prototype = new F();
    
      var instanceA = new Child('kevin', '18');
    
      console.log(instanceA);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    foo

    封装继承方法

    function prototype(child, parent) {
        var prototype = Object.create(parent.prototype);
        prototype.constructor = child;
        child.prototype = prototype;
    }
    
    // 当我们使用的时候:
    prototype(Child, Parent);
    
    1
    2
    3
    4
    5
    6
    7
    8

    以上继承实现的核心就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数。

# Class 继承

以上两种继承方式都是通过原型去解决的,在 ES6 中,我们可以使用 class 去实现继承,并且实现起来很简单

class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
  }
}
const a = new Parent()
console.log(a)
const b = new Child('1')
console.log(b)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

class 实现继承的核心在于使用 extends 表明继承自哪个父类,并且在子类构造函数中必须调用 super,因为这段代码可以看成 Parent.call(this, value)

Last Updated: 8/13/2021, 5:09:05 PM