Class的继承是通过extends关键字来实现的,相比较ES5中通过修改原型链来实现继承更加的清晰。
class Parent {
constructor (name, age) {
this.name = name
this.age = age
}
getUserInfo() {
console.log(this.name, this.age)
}
}
class Child extends Parent {
constructor(name, age) {
super(name, age)
this.hobby = ['running', 'reading']
}
}
const child = new Child('fuhangyy', 27)
child.getUserInfo()
// fuhangyy 27
console.log(child.hobby)
// ['running', 'reading']
console.log(child instanceof Child)
// true
console.log(child instanceof Parent)
// true
我们可以看到在子类的constructor方法调用了一个super方法,它在这里主要表示父类的构造函数,用来新建父类的this对象。
并且在子类的constructor方法中必须调用super方法,否则在新建子类的实例时是会报错的。因为子类中其实是没有this对象的,而是需要继承父类的this对象,然后对其加工,所以如果不调用super方法,也就得不得父类的this对象,子类也就没法对其进行加工,从而得不到自己的this对象了。
还可以看出来实例对象child同时是Child和Parent来个类的实例,这与ES5的继承行为是一致的。
class Parent {}
class Child extends Parent {
constructor() {}
}
const child = new Child()
上面的示例代码中,用于在子类Child中没有调用super方法,导致在创建child示例时报错。
ES5中的继承的本质其实是先创建子类的实例对象this,然后再将父类的方法添加到this上面去(Parent.call(this))。但是Class的继承机制完全不同,它是先创建父类的实例对象的this(调用super方法),然后再用子类的构造函数修改this。
我们都知道在使用Class定义类的时候,如果没有添加constructor方法时,都会默认添加的,在继承时也不例外:
class Child extends Parent {}
// 等价于
// 上面的例子中调用了consturctor方法,但是没有调用super方法,会报错的
class Child extends Parent {
constructor (...arguments) {
super(...arguments)
}
}
还有一点需要注意的是,在子类中只有调用了super方法,才可以使用this对象,原因上面已经说过了。
class Child extends Parent {
constructor(name, age) {
this.hobby = ['running', 'reading'] // 这里使用this时是会报错
super(name, age)
this.hobby = ['running', 'reading']
}
}
这里补充一个小知识,我们可以通过Object.getPrototypeOf()方法从子类上获取到父类。
Object.getPrototypeOf(Child) === Parent
// true
下面来一起看看super关键字,它既可以当函数来调用,也可以当做对象来使用,这两种情况下,他们的用法是完全不同的。
当super被当做函数来调用时,它代表的父类的构造函数,用来新建父类的this对象,提供给子类使用。所以在子类的constructor方法中必须要调用一次super方法,而且是只能在子类的constructor方法中调用,在其他地方调用就会导致报错。
super虽然代表了父类Parent的构造函数,但是返回的是子类Child的实例,即super内部的this指向的是子类Child,因此super()在这里相当于Parent.prototype.constructor.call(this)。
当super作为对象在普通方法中使用时指向父类的原型对象,在静态方法中使用时指向父类。
class Parent {
constructor (name) {
this.name = name
}
getName () {
console.log(this.name)
}
}
class Child extends Parent {
constructor (name) {
super(name)
super.getName()
// fuhangyy
console.log(super.name)
// undefined
}
}
const child = new Child('fuhangyy')
由于class类的其他方法默认是添加在类的原型上面的,所以在调用super.getName()时,调用的父类原型对象上面的方法,所以可以打印出数据,但是name属性是定义在父类的实例中的,所以值为undefined。
接下来,我们看看下面这个例子:
class Parent {
constructor () {
this.name = 'fuhangyy'
}
getName () {
console.log(this.name)
}
}
class Child extends Parent {
constructor () {
super()
this.name = 'RubyOnly'
}
getUserInfo () {
super.getName()
}
}
const child = new Child()
child.getUserInfo()
// RubyOnly
child.getName()
// RubyOnly
调用child.getName(),打印出RubyOnly很好理解,child继承了Parent中的getName方法,但是我们调用child.getUserInfo()打印出来的也是RubyOnly,super.getName()虽然调用的是Parent.prototype.getName(),但其实 Parent.prototype.getName()会绑定Child的this,所以最后打印出来的是RubyOnly,因此我们可以总结出,通过super调用父类方法时,会绑定子类的this。
既然通不过super调用父类的方法是会将this绑定到子类上,那么通过super修改父类的某个属性时,被修改的属性就变成了子类的实例属性了。
class Parent {
constructor () {
this.name = 'fuhangyy'
}
}
class Child extends Parent {
constructor () {
super()
super.name = 'RubyOnly'
console.log(super.name)
// undefined 在普通函数中super调用的是父类原型对象上面的属性和方法
console.log(this.name)
// RubyOnly
}
}
const child = new Child()
但是如果super在静态方法中调用,此时super指向的是父类。
class Parent {
static getName (name) {
console.log(`static ${ name }`)
}
getName(name) {
console.log(`prototype ${ name }`)
}
}
class Child extends Parent {
static getName (name) {
super.getName(name)
}
getName (name) {
super.getName(name)
}
}
Child.getName('fuhangyy')
// static fuhangyy
const child = new Child()
child.getName('fuhangyy')
// prototype fuhangyy
我们可以看出来,super在静态方法中指向了父类,在普通函数中指向了父类的原型。