super关键字

2020/05/23 Javascript 共 2518 字,约 8 分钟

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

superthis

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)
    

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

首先,我们将o1o2理解为两个原型链,则有:

  • 原型链o1o2存在同名方法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] 原型属性屏蔽

Search

    Table of Contents