JS继承-ES6中Class继承

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示例时报错。

QQ图片20200407105649.png

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在静态方法中指向了父类,在普通函数中指向了父类的原型。

阅读 128

推荐阅读