9 实现类的继承

1 实现类的继承-简版

类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。

    // 寄生组合继承
    function Parent(name) {
      this.name = name
    }
    Parent.prototype.say = function() {
      console.log(this.name + ` say`);
    }
    Parent.prototype.play = function() {
      console.log(this.name + ` play`);
    }
    
    function Child(name, parent) {
      // 将父类的构造函数绑定在子类上
      Parent.call(this, parent)
      this.name = name
    }
    
    /** 
     1. 这一步不用Child.prototype = Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
     2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
    3. Object.create是创建了父类原型的副本,与父类原型完全隔离
    */
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.say = function() {
      console.log(this.name + ` say`);
    }
    
    // 注意记得把子类的构造指向子类本身
    Child.prototype.constructor = Child;
    // 测试
    var parent = new Parent('parent');
    parent.say() 
    
    var child = new Child('child');
    child.say() 
    child.play(); // 继承父类的方法

2 ES5实现继承-详细

第一种方式是借助call实现继承

    function Parent1(){
        this.name = 'parent1';
    }
    function Child1(){
        Parent1.call(this);
        this.type = 'child1'    
    }
    console.log(new Child1);

这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类中一旦存在方法那么子类无法继承。那么引出下面的方法

第二种方式借助原型链实现继承:

      function Parent2() {
        this.name = 'parent2';
        this.play = [1, 2, 3]
      }
      function Child2() {
        this.type = 'child2';
      }
      Child2.prototype = new Parent2();
    
      console.log(new Child2());

看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:

      var s1 = new Child2();
      var s2 = new Child2();
      s1.play.push(4);
      console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]

明明我只改变了s1的play属性,为什么s2也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象

第三种方式:将前两种组合:

    function Parent3 () {
        this.name = 'parent3';
        this.play = [1, 2, 3];
      }
      function Child3() {
        Parent3.call(this);
        this.type = 'child3';
      }
      Child3.prototype = new Parent3();
      var s3 = new Child3();
      var s4 = new Child3();
      s3.play.push(4);
      console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]

之前的问题都得以解决。但是这里又徒增了一个新问题,那就是Parent3的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是我们不愿看到的。那么如何解决这个问题?

第四种方式: 组合继承的优化1

      function Parent4 () {
        this.name = 'parent4';
        this.play = [1, 2, 3];
      }
      function Child4() {
        Parent4.call(this);
        this.type = 'child4';
      }
      Child4.prototype = Parent4.prototype;

这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问,但是我们来测试一下

      var s3 = new Child4();
      var s4 = new Child4();
      console.log(s3)

子类实例的构造函数是Parent4,显然这是不对的,应该是Child4。

第五种方式(最推荐使用):优化2

      function Parent5 () {
        this.name = 'parent5';
        this.play = [1, 2, 3];
      }
      function Child5() {
        Parent5.call(this);
        this.type = 'child5';
      }
      Child5.prototype = Object.create(Parent5.prototype);
      Child5.prototype.constructor = Child5;

这是最推荐的一种方式,接近完美的继承。

Last Updated:
Contributors: leeguooooo