super关键字
super
是什么?
super
含义为superClass(超类),即表示当前类的父类/祖先类。
super
具有以下特点:
- 在构造器中,
super
指向”父构造器“。可以通过super()
方法调用parent.constructor
- 在方法中,
super
指向”父对象“。可以通过super.fn
调用父方法 super
是静态绑定的,不能通过call/apply
等方式通过改变this实例的方式改变super
指向
理解super
的静态绑定特性
class ParentA {
constructor() {
this.id = 'a'
}
foo() {
console.log('ParentA:', this.id)
}
}
class ParentB {
constructor() {
this.id = 'b'
}
foo() {
console.log('ParentB:', this.id)
}
}
class ChildA extends ParentA {
foo() {
super.foo()
console.log('ChildA:', this.id)
}
}
class ChildB extends ParentB {
foo() {
super.foo()
console.log('ChildB:', this.id)
}
}
let a = new ChildA()
a.foo() // ParentA: a // ParentB: b
let b = new ChildB()
b.foo() // ParentB: b // ChildB: b
// 将this重新绑定到实例a
b.foo.call(a) // ParentB: a // ChildB: a
通过以上的例子我们可以得知:
- 我们通过改变this指向的方式,将this指向了实例
a
- 虽然log输出了
a
,但是log头部依旧为ParentB
- 这是因为this指针是动态的,但是super却是静态的
super
的使用顺序
class Foo {
constructor() {
this.a = 1
}
}
class Bar extends Foo {
constructor() {
super()
this.b = 2
// 在调用super之前访问this会报错
// super()
}
}
发生这个问题的原因:
- 创建/初始化子类实例this的实际上是父构造器,所以需要先调用
super
再访问this
super
与this
function Foo() {
this.a = 1
}
function Bar() {
this.b = 2
console.log('[this] should be [{b: 2}]: ', this);
// 需要注意这里,相当于使用this作为上下文执行了Foo
// 执行Foo的结果是,在this上下文创建了值为1的属性a
Foo.call(this)
console.log('[this] should be [{b: 2, a: 1}]: ', this);
}
// Bar extends Foo
Bar.prototype = Object.create(Foo.prototype)
let bar = new Bar()
需要注意:
Foo.call(this)
相当于以下三行:this.Foo = Foo this.Foo() delete this.Foo
Bar extends Foo
的等价实现:Bar.prototype = Object.create(Foo.prototype)
- 这里
Bar.prototype.constructor
已经变更为了Foo
,原因参考实例化与原型链变更
- 这里
super
关键字于普通对象中的使用
let o1 = {
foo() {
console.log('this is o1.foo')
}
}
let o2 = {
foo() {
super.foo()
console.log('this is o2.foo')
}
}
// 将o2的原型链设置为o1,此时o2中的super相当于Object.getPrototypeOf(o2),即o1
Object.setPrototypeOf(o2, o1)
console.log('it should be [o1]:', o2.__proto__)
o2.foo() // this is o1.foo // this is o2.foo
需要注意,在普通对象中使用时:
- 因为没有
constructor
,所以不能通过super()
的形式调用 - 仅作为静态
prototype
委托指针使用
class中super
关键字的作用
解决原型链上层同名函数的多态调用问题
首先思考下面的例子:
let o1 = {
foo() {
console.log('this is o1.foo')
}
}
let o2 = {
foo() {
console.log('this is o2.foo')
},
foo1() {
o1.foo.call(this)
}
}
Object.setPrototypeOf(o2, o1)
o2.foo() // this is o1.foo
o2.foo1() // this is o1.foo
首先,我们将o1
和o2
理解为两个原型链,则有:
- 原型链
o1
和o2
存在同名方法foo - 我们在实例化原型链
o2
时,因为同名属性于原型上会发生屏蔽(原型属性屏蔽),拿我们如何调用o1.foo
呢?- 这里就是显式多态,即我们通过显式声明的方式
o1.foo.call(this)
调用o1.foo
方法 - 因为在
o2
作用域内需要调用o1
,假如类似的场景变多,代码量变大以后,o1
这种变量引用就会很多,维护也会很不方便(可以考虑一种重构场景,o1
更名为obj1
,此时维护难度就会变大,也容易出问题)
- 这里就是显式多态,即我们通过显式声明的方式
那有没有一种显式的声明方式,可以把这种引用提升到顶层处理呢?
class O1 {
foo() {
console.log('this is o1.foo')
}
}
class O2 extends O1 {
foo() {
console.log('this is o2.foo')
}
foo1() {
super.foo()
}
}
let o2 = new O2()
o2.foo() // this is o1.foo
o2.foo1() // this is o1.foo
此时,我们可以通过super
关键字进行多态调用:
- 一定程度将多态关系提升到顶层进行维护,体现于
extends
关键字的声明 - 此时在class内部,我们可以通过
super
关键字去访问
[1] 实例化与原型链变更
[2] 原型属性屏蔽