【JS】面向对象-类继承
类继承:
- 可以实现一个类扩展另一个类
- 使用extends关键字进行类继承
class Child extends Parent
1. extends内部机制
- 使用原型机制,
Child.prototype.[[Prototype]] = Parent.prototype 用于继承常规方法和属性
Child.[[prototype]] = Parent 用于继承静态方法和属性
- 子类可以访问父类的方法
class Animal{
constructor(name){
this.name = name
}
run(){
console.log('animal run');
}
}
class Dog extends Animal{
}
let d = new Dog('dog')
d.run() // 'animal run'
2.方法重写
2.1 完全重写
class Animal{
constructor(name){
this.name = name
}
run(){
console.log('animal run');
}
}
class Dog extends Animal{
run(){
console.log('dog run');
}
}
let d = new Dog('dog')
d.run() // 'dog run'
2.2 方法扩展
- 当不希望完全重写父类方法时,可以使用super关键字,执行super.method()来调用一个父类的方法
class Animal{
constructor(name){
this.name = name
}
run(){
console.log('animal run');
}
}
class Dog extends Animal{
run(){
super.run()
console.log('dog run');
}
}
let d = new Dog('dog')
d.run() // 'animal run' 'dog run'
* 箭头函数没有super
3. constructor重写
- 若子类不写自己的constructor,则默认会有一个空的constructor,里面有super(),也就是调用了父类的constructor,并将所有参数传了进去。
类似这样:
class Dog extends Animal{
constructor(...args){
super(...args)
}
}
- 执行super(...args)可以调用父类的constructor
- 若子类写了constructor,需要在this语句前调用super(),否则会报错:
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
在访问“this”或从派生构造函数返回之前,必须在派生类中调用super
原因:
JS中继承类的构造函数中有一个特殊的属性[[Constructorkind]]:"derived",这个内部标签会影响它的new行为,让继承类中的constructor无法正常工作,普通函数使用new时,会创建一个空对象并将this指定为该对象,但在这里继承类使用new时,类中的constructor不会这样做,而是期待父类中的constructor来完成这项操作。
4.类字段重写
父类构造器中只会使用自己字段的值,而不是重写的
原因:
类字段初始化的顺序不一样
- 对于基类,在调用构造函数前初始化
- 对于派生类,则是在调用完super()后才进行初始化
所以,new Dog()后要执行Dog中constructor函数,所以会先执行super调用父类中的constructor。执行父类的constructor时,父类中字段已经初始化好了,但子类还没有,所以使用时会使用父类中的字段。
5.super原理
当一个对象方法执行时,它会将当前对象作为 this。随后如果我们调用 super.method(),那么引擎需要从当前对象的原型中获取 method。但仅仅依靠this无法实现,有时this指向的对象不是我们想要的。
JavaScript中在函数中有一个特殊的内部属性[[HomeObject]],当一个函数被定义为类或对象中的方法时,该函数中的[[HomeObject]]属性就成为该对象。然后super就使用它接卸父原型及其方法。
const person = {
name: 'person',
say(){
console.log(this.name,'say');
}
}
const man = {
__proto__: person,
name:'man',
say(){ // man.say.[[HomeObject]] == man
super.say()
}
}
man.say()
在 JavaScript 语言中 [[HomeObject]] 仅被用于 super。所以,如果一个方法不使用 super,那么我们仍然可以视它为自由的并且可在对象之间复制。
const person = {
name: 'person',
say(){
console.log(this.name,'person say');
}
}
const man = {
__proto__: person,
name:'man',
say(){ // man.say.[[HomeObject]] == man
super.say()
}
}
man.say()
const animal = {
name:'animal',
say(){
console.log(this.name,'animal say');
}
}
const cat = {
__proto__: animal,
name:'cat',
say: man.say
}
cat.say() //cat person say
cat中的say: man.say == { // man.say.[[HomeObject]] == man
super.say()
}
由于有super,所以会找[[HomeObject]],此时的[[HomeObject]]是man,因为是在man中创建的,[[HomeObject]]已经被永久绑定了。所以会找man对象原型上的say方法。
[[HomeObject]] 是为类和普通对象中的方法定义的。但是对于对象而言,方法必须确切指定为 method(),而不是 "method: function()"。