JavaScript中class继承超乎你的想象《二》

653 阅读3分钟

接着上一篇,继续探讨javascript中class的另外用法:

Object.getPrototypeOf()

Object.getPrototypeOf方法可以用来从子类上获取父类。

Object.getPrototypeOf(Dog)===Aimals
//true

因此我们可以使用这个方法判断一个类是否继承了另外一个类就很方便了。

super关键字的用法

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

1.第一种情况,super作为函数调用父类的构造函数,ES6要求,子类的构造函数必须执行一次super函数:

class A{
    
}
class B extends A{
    constructor(){
        super()
    }
}

以上的代码,在子类的构造函数中必须使用super()去代表调用父类的构造函数,这个是必须的,否则javascript引擎会报错。

super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B,因此supe()在这里相当于A。prototype.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{
    constructor(){
        super()//使用方式正确
    }
    myMthod(){
        super()//报错,使用方式错误
    }
}

2.第二种情况,super作为对象在普通的方法中指向父类的原型对象在静态方法中指向父类

class A{
    p(){
        retuen 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实例中的方法,子类B通过super.p是引用不到的。如果这样定义在A的原型对象那么就没有问题:

class A{
    A.prototype.p=2
}
class B extends A{
    super()
    console.log(super.x)//2
}

let b=new B()

上面的代码,属性P是定义在A.prototype上的,所以super.x可以取到它的值。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)//undefind
        console.log(this.x)//3
    }
}
let b=new B()

上面的代码super.x被赋值为3,等同于this.x=3.读取super.x时,相当于读取的是A.prototype.x,而父类的原型上并没有这个属性,所以undefined。

但是如果super作为对象再静态方法中使用,这个时候super将指向父类,而不是父类的原型对象。

class Parent(){
    static myMethods(msg){
        console.log('static',msg)
    }
    myMethods(msg){
        console.log('instance',msg)
    }
}
class Child extends Parent(){
    static myMethods(msg){
        super.myMethods(msg)
    }
    myMethods(msg){
        super.myMethods(msg)
    }
}
Child.myMethods(1)//static 1
let child=new Child()
child.myMethods(2)//instance 2

上面的代码,super在静态方法中指向父类,在普通方法中指向父类的原型,所以使用super的时候一定要显示定义是作为函数还是对象使用,否则会报错。看如下:

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