类继承是一个类扩展另一个类的一种方式,我们可以在现有功能之上创建新功能。
extends 关键字
假设我们有一个普通动物的类 class Animal,包括一般动物都有的特性:
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
stop() {
this.speed = 0;
alert(`${this.name} stands still.`);
}
}
let animal = new Animal("My animal");
现在我们要创建一个小兔子类 class Rabbit,除了拥有普通动物都有的特性外,它还有专属于自己的特性,这时我们就用到了继承:class Rabbit extends Animal
。
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!
class Rabbit
的对象可以访问例如 rabbit.hide()
等 Rabbit
的方法,还可以访问例如 rabbit.run()
等 Animal
的方法。
重写方法
默认情况下,所有未在 class Rabbit
中指定的方法均从 class Animal
中直接获取。但如果我们在 Rabbit
中指定了我们自己的方法,例如 stop()
,我们可以这样写:
class Rabbit extends Animal {
stop() {
// ……现在这个将会被用作 rabbit.stop()
// 而不是来自于 class Animal 的 stop()
}
}
但是通常我们并不会完全替换父类的方法,而是在父类的基础上进行扩展,为此 class 特别提供了 super 关键字:
- 执行
super.method(...)
来调用一个父类方法。 - 执行
super(...)
来调用一个父类 constructor(只能在我们的 constructor 中)。
例如,让我们的 rabbit 在停下来的时候自动 hide:
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
run(speed) {
this.speed = speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
stop() {
this.speed = 0;
alert(`${this.name} stands still.`);
}
}
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
stop() {
super.stop(); // 调用父类的 stop
this.hide(); // 然后 hide
}
}
let rabbit = new Rabbit("White Rabbit");
rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.stop(); // White Rabbit stands still. White Rabbit hides!
注意:箭头函数没有 super,如果被访问,它会从外部函数获取。
重写 constructor
根据规范,如果一个类扩展了另一个类且没有 constructor,那么将生成一个空的 constructor
class Rabbit extends Animal {
// 为没有自己的 constructor 的扩展类生成的
constructor(...args) {
super(...args);
}
}
同时必须注意,如果要自定义 constructor,继承类的 constructor 必须调用 super(...)
,并且 (!) 一定要在使用 this
之前调用。
在 JavaScript 中,继承类(所谓的“派生构造器”,英文为 “derived constructor”)的构造函数与其他函数之间是有区别的。派生构造器具有特殊的内部属性 [[ConstructorKind]]:"derived"
。这是一个特殊的内部标签。
该标签会影响它的 new
行为:
- 当通过
new
执行一个常规函数时,它将创建一个空对象,并将这个空对象赋值给this
。 - 但是当继承的 constructor 执行时,它不会执行此操作。它期望父类的 constructor 来完成这项工作。
因此,派生的 constructor 必须调用 super
才能执行其父类(base)的 constructor,否则 this
指向的那个对象将不会被创建。并且我们会收到一个报错。
为了让 Rabbit
的 constructor 可以工作,它需要在使用 this
之前调用 super()
,就像下面这样
class Animal {
constructor(name) {
this.speed = 0;
this.name = name;
}
// ...
}
class Rabbit extends Animal {
constructor(name, earLength) {
super(name);
this.earLength = earLength;
}
// ...
}
// 现在可以了
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
这里也有一点需要注意:对于基类与派生类字段初始化的顺序是不同的
- 对于基类(还未继承任何东西的那种),在构造函数调用前初始化。
- 对于派生类,在
super()
后立刻初始化。
同时,父类构造器总是会使用它自己字段的值,而不是被重写的那一个。
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!