ES6 Class继承

1,983 阅读4分钟

class 中继承主要是依靠两个东西:

  • extends
  • super

而且对于该继承的效果和寄生组合继承方式一样。

继承规则

class Parent {
}
​
class Child extends Parent {
}

上面代码定义了一个Child类,该类通过extends关键字,继承了Parent类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。

如果想在子类中添加属性与方法,就可以:

class Parent {
    constructor(name, sex) {
        this.name = name
        this.sex = sex
    }
    sing() {
        console.log(this.sex);
    }
}
​
class Child extends Parent {
    constructor(name, sex, num) {
        super(name, sex)  // 调用父类的constructor(x, y)
        this.num = num
    }
    song() {
        console.log(this.name);
    }
}

constructor

子类必须在constructor方法中调用super方法

  • 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
class Parent { /* ... */ }
​
class Child extends Point {
  constructor() {
  }
}
​
let cp = new Child(); // ReferenceError
  • 由于上述原因,constructor方法中的super方法必须先定义,然后才能调用this对象。
class Parent {
    constructor() {}
}
​
class Child extends Parent {
    constructor() {
        this.num = 10//报错
        super()
        this.num = 10//正确写法
    }
    
}
  • 如果子类没有定义constructor方法,这个方法会被默认添加
class Child extends Parent {
}
​
// 等同于
class Child extends Parent {
  constructor(...args) {
    super(...args);
  }
}

super

  • super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

super函数

  • super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
  • 如果子类中没有定义constructor方法,这个方法会被默认添加,相当于已经执行了一次super函数。
  • super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)
  • 作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。

super对象

  • super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
  p() {
    return 2;
  }
}
​
class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}
​
let b = new B();

上面代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法之中,指向A.prototype,所以super.p()就相当于A.prototype.p()

  • 由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
class Parent {
    constructor(name, sex) {
        this.name = name
        this.sex = sex
    }
    sing() {
        console.log(this.sex);
    }
}
​
class Child extends Parent {
    song() {
        super.sing();
    }
    tell() {
        console.log(super.name);
    }
}
​
let xx = new Child('xx', 'boy')
​
xx.song()  //boy
xx.tell()  //undefined
  • 如果属性定义在父类的原型对象上,super就可以取到。

  • ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。

  • 如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。

  • 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。

  • 注意,使用super的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。

    class A {}
    ​
    class B extends A {
      constructor() {
        super();
        console.log(super); // 报错
      }
    }
    

    上面代码中,console.log(super)当中的super,无法看出是作为函数使用,还是作为对象使用,所以 JavaScript 引擎解析代码的时候就会报错。这时,如果能清晰地表明super的数据类型,就不会报错。

总结

ES6中的继承:

  • 主要是依赖extends关键字来实现继承,且继承的效果类似于寄生组合继承
  • 使用了extends实现继承不一定要constructorsuper,因为没有的话会默认产生并调用它们
  • extends后面接着的目标不一定是class,只要是个有prototype属性的函数就可以了

super相关:

  • 在实现继承时,如果子类中有constructor函数,必须得在constructor中调用一下super函数,因为它就是用来产生实例this的。
  • super有两种调用方式:当成函数调用和当成对象来调用。
  • super当成函数调用时,代表父类的构造函数,且返回的是子类的实例,也就是此时super内部的this指向子类。在子类的constructorsuper()就相当于是Parent.constructor.call(this)
  • super当成对象调用时,普通函数中super对象指向父类的原型对象,静态函数中指向父类。且通过super调用父类的方法时,super会绑定子类的this,就相当于是Parent.prototype.fn.call(this)

引用 # 💦【何不三连】做完这48道题彻底弄懂JS继承(1.7w字含辛整理-返璞归真)

# Class 的继承