主题
class 继承
继承表示“在已有类的基础上继续扩展”。子类可以复用父类的属性和方法,再添加自己的能力。
基本写法
使用 extends 让一个类继承另一个类:
js
class Animal {
constructor(name) {
this.name = name
this.speed = 0
}
run(speed) {
this.speed = speed
console.log(`${this.name} runs with speed ${this.speed}`)
}
}
class Rabbit extends Animal {
hide() {
console.log(`${this.name} hides`)
}
}
const rabbit = new Rabbit('White Rabbit')
rabbit.run(5)
rabbit.hide()这里 Rabbit 自己只定义了 hide(),但依然可以调用从 Animal 继承来的 run()。
继承到底继承了什么
最实用的一层理解是:
- 子类实例可以访问父类原型上的方法。
- 子类也可以有自己的方法。
- 子类和父类依然是两个不同的类。
js
class Animal {
run() {}
}
class Rabbit extends Animal {
hide() {}
}这时:
rabbit.hide()来自Rabbit.prototyperabbit.run()来自Animal.prototype
原型链上的关系
extends 本质上还是建立在原型机制之上:
js
class Animal {}
class Rabbit extends Animal {}
console.log(Object.getPrototypeOf(Rabbit.prototype) === Animal.prototype) // true
console.log(new Rabbit() instanceof Animal) // true也就是说,类继承只是把原型链组织得更清晰。
重写方法
子类可以定义与父类同名的方法,这会覆盖父类实现:
js
class Animal {
speak() {
console.log('animal sound')
}
}
class Dog extends Animal {
speak() {
console.log('wang')
}
}调用时会优先命中子类自己的实现。
super
在方法里调用父类方法
如果子类不是完全替换,而是想在父类逻辑基础上扩展,可以使用 super:
js
class Animal {
stop() {
this.speed = 0
console.log(`${this.name} stops`)
}
}
class Rabbit extends Animal {
stop() {
super.stop()
this.hide()
}
hide() {
console.log(`${this.name} hides`)
}
}这里的 super.stop() 表示先执行父类版本,再追加子类自己的行为。
在 constructor 里调用父类构造器
js
class Animal {
constructor(name) {
this.name = name
}
}
class Rabbit extends Animal {
constructor(name, earLength) {
super(name)
this.earLength = earLength
}
}子类 constructor 里的硬性规则
在继承场景下,如果子类自己写了 constructor,就必须先调用 super(),之后才能访问 this:
js
class Animal {
constructor(name) {
this.name = name
}
}
class Rabbit extends Animal {
constructor(name) {
super(name)
this.type = 'rabbit'
}
}下面这种写法会报错:
js
class Rabbit extends Animal {
constructor(name) {
// this.name = name // ReferenceError
super(name)
}
}原因可以直接记成一句话:子类实例的初始化要先交给父类打底。
不写 constructor 时会怎样
如果子类没有显式声明 constructor,JavaScript 会帮它补一个等价版本:
js
class Rabbit extends Animal {
constructor(...args) {
super(...args)
}
}所以很多简单子类根本不需要手写构造函数。
继承静态成员
继承的不只是实例方法,静态成员也会沿着类本身继续传递:
js
class Animal {
static category = 'creature'
static create() {
return new this()
}
}
class Rabbit extends Animal {}
console.log(Rabbit.category) // 'creature'
console.log(Rabbit.create() instanceof Rabbit) // trueextends 后面不一定非要是类名
只要最终能得到一个可继承的构造器,也可以放在 extends 后面:
js
function createBase() {
return class {
sayHi() {
console.log('hi')
}
}
}
class User extends createBase() {}这种写法存在,但日常业务里并不常见。大多数情况下,直接继承一个明确类名更直观。
什么时候适合用继承
继承更适合“明确的 is-a 关系”:
Rabbit是一种AnimalAdminUser是一种User
如果只是想复用部分能力,而不是表达类型层级,通常更适合:
- 组合
- 工具函数
- mixin
使用建议
- 只有在确实存在明确层级关系时,再使用继承。
- 子类重写方法时,先想清楚是完全替换还是在父类逻辑上扩展。
- 子类写
constructor时,先调用super()再访问this。 - 复用逻辑如果只是横切能力,不要优先想到继承。
