第二十章 Class的继承

简介

  Class可以通过extends关键字实现继承,这比ES5通过修改原型链实现继承更加清晰和方便。

    class Point {
        // ...
    }
    class ColorPoint extends Point {
        constructor (x, y, color) {
            super(x, y)           // 调用父类的Constructor(x, y)
            this.color = color
        }
        toString () {
            return this.color + '' + super.toString()   // 调用父类的toString()
        }
    }

  上面代码中出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
  子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

class Point {}
class ColorPoint extends Point {
    constructor () {}
}
let cp = new ColorPoint()   // 报错

  ES5的继承实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this,子类的this暂时还没有,所以必须先调用super方法,然后再用子类的构造函数修改this。

// ES5继承
let a = new ColorPoint()
function Point () {}
a.prototype = new Point()

  如果子类没有定义constructor方法,那么这个方法会被默认添加,无论有没有显示定义,任何一个子类都有constructor方法。

    class ColorPoint extends Point {}
    // 等同于
    class ColorPoint extends Point {
        constructor (...args) {
            super(...args)
        }
    }

  另一个需要注意的地方是,在子类的构造函数中,只有调用super之后才可以使用this关键字,否则会报错。这是因为子类实例的构建是基于对父类实例加工,只有super方法才能返回父类实例。

    class Point {
        // ...
    }
    class ColorPoint extends Point {
        constructor (x, y, color) {
            super(x, y)           // 调用父类的Constructor(x, y)
            this.color = color
        }
    }
    let cp = new ColorPoint(25, 8, 'green')
    cp instanceof ColorPoint   // true
    cp instanceof Point        // true

  上面的代码中,实例对象cp同时是ColorPoint和Point两个类的实例,这与ES5的行为完全一致。
调用super(x, y)方法可以暂时理解为父类的实例new Point(x, y)

Object.getPrototypeOf()

  Object.getPrototypeOf方法可以用来从子类上获取父类。Object.getPrototypeOf(ColorPoint) === Point // true
因此可以使用这个方法判断一个类是否继承了另一个类。

super关键字

  super关键字既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
  第一种情况,super作为函数调用时代表父类的构造函数。ES6要求,子类的构造函数必须执行一次super函数。
  super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此super()在这里相当于A.prototype.constructor.call(this)。

    class A {
        constructor () {
            console.log(new.target.name)
        }
    }
    class B extends A {
        constructor () {
            super()
        }
    }
    new A()  // A
    new B()  // B

  上面代码中,new.target指向当前正在执行的函数。可以看到,在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数。也就是说,super()内部的this指向的是B。
  作为函数时,super()只能在子类的构造函数之中,用在其他地方就会报错。

    class A {}
    class B extends A {
        m () {
            super()   // 报错
        }
    }

  上面的代码中,super()用在B类的m方法之中就会造成语法错误。
  第二种情况,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 A {
        constructor () {
            this.p = 2
        }
    }
    class B extends A {
        get m () {
            return super.p
        }
    }
    let b = new B()
    b.m // undefined

  上面代码中,p是父类A实例的属性,因此super.p就引用不到。
  ES6规定,通过super调用父类的方法时,super会绑定子类的this。

    class A {
        constructor () {
            this.x = 1
        }
        print () {
            console.log(this.x)
        }
    }
    class B extends A {
        constructor () {
            super()
            this.x = 2
        }
        m () {
            super.print()
        }
    }
    let b = new B()
    b.m // 2

  上面的代码中,super.print()虽然调用的时A.prototype.print(),但是A.prototype.print()会绑定子类B的this,导致输出的时2,而不是1。也就是说,实际上执行的时super.print.call(this)。
  由于绑定子类的this,因此如果通过super对某个属性赋值,这时super就是this,赋值的属性就会变成子类实例的属性。

    class A {
        constructor () {
            this.x = 1
        }
    }
    class B extends A {
        constructor () {
            super()
            this.x = 2
            super.x = 3
            console.log(super.x) // undefined
            console.log(this.x)  // 3
        }
    }
    let b = new B()

  上面的代码中,super.x被赋值为3,等同于对this.x赋值为3。当读取super.x时,相当于读取的是A.prototype.x,所以返回undefined。
  如果super作为对象用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
总结:super作为函数调用时代表父类的构造函数,也就是说super表示Point构造函数,super()表示调用父类的构造函数,构造函数的调用需要用new,也就是new Point(),super方法才能返回父类实例,也说明了这一点。super()在这里相当于A.prototype.constructor.call(this).super作为对象时在普通方法中指向父类的原型对象;在静态方法中指向父类。
问题:super()到底表示什么?为什么一定需要super,子类没有this是什么意思,this指向是怎么回事?
注意:当一个普通的构造函数运行时,它会创建一个空对象作为 this,然后继续运行,但是当派生的构造函数运行时,与上面说的不同,它指望父构造函数来完成这项工作,所以如果我们正在构造我们自己的构造函数,那么我们必须调用 super,否则具有 this 的对象将不被创建,并报错。

子类没有自己的this对象,而是继承父类的this对象。如果不调用super方法,子类就得不到this对象。获取到父类的this对象之后,这个this对象就会指向子类的实例。第一种情况super作为函数调用时代表父类的构造函数。super虽然表示父类的构造函数,但是调用的时候返回的时子类的实例,因为super内部的this指向的是B,因此super()在这里相当于A.prototype.constructor.call(this)。第二种情况super作为对象时在普通方法中指向父类的原型对象,在静态方法中指向父类。

类的prototype属性和proto属性

  • 子类的proto属性表示构造函数的继承,总是指向父类。
  • 子类的prototype属性的proto属性表示方法的继承,总是指向父类的prototype属性。
class A {}
class B extends A {}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype  // true

你可能感兴趣的:(第二十章 Class的继承)