super 关键字的一点思考
class A {}
class B extends A {
constructor() {
super() // KEY
}
}
let b = new B(); // this? prototype呢
我们今天从一个简单的面试题出发,开始逐步去认识 super 是如何去使用的?
这时我们提出几个简单的问题:
- b实际上指向的是谁?
- A 和 B 之间存在着怎样的关系?
- super 到底做了什么事情呢?
开始正式步入正题
在实际使用super的时候 会分为函数调用和对象调用
第一种情况 super 作为函数调用
super 作为函数调用的时候,代表父类的构造函数. es6要求, 子类的构造函数必须执行一次super函数
class A {}
class B extends A {
constructor() {
super();
}
}
上面代码中, 子类B的构造函数中的 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() 指向的是 B.
第二种情况 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在普通方法中, 指向的是 A的 prototype, 所以 super.p() 就相当于 A.prototype.p()。
这里需要注意几点
- 由于
super指向父类的原型对象, 所以定义在父类实例上的方法或者属性是无法通过super调用的 - 但是 如果是定义在父类的原型上的话,
super就可以取到的
class A {}
A.prototype.child = 'jiuwo';
class B extends A {
constructor() {
super();
console.log(super.child) // 'jiuwo'
}
}
let b = new B()
上面的代码中, 属性 child 是定义在 A.prototype , 所以呢 我们可以通过 super.child 取到它的值。
ES6 规定, 在子类普通方法中通过 super 调用父类的方法时, 方法内部的 this指向当前的子类的实例。
class A {
constructor() {
this.x = 1;
}
get() {
console.log(this.x)
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
child() {
super.get();
}
}
let b = new B();
b.child() // 2
上面的代码, 我们可以看到 super.child() 虽然调用的是 A.prototype.get(),但是实际上呢, A.prototype.get() 内部调用的是子类B的实例, 所以最后导致的结果就是 2,而不是 1. 也就是说呢, 实际上执行的是, super.get.call(this).
由于 this会指向子类的实例这个特性, 所以呢 当我们通过 super 对某个属性赋值的时候, 这时的 super 就是 this, 赋值的属性会变成子类实例的属性。
所以到这里 我们的学习似乎暂时要结束了,你是否能解答出文章的开头几个问题呢?