# 面向对象-类继承
类继承是一个类扩展另一个类的一种方式。
因此,我们可以在现有功能之上创建新功能。
# 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:
因为 rabbits 是 animals,所以 class Rabbit 应该是基于 class Animal 的,可以访问 animal 的方法,以便 rabbits 可以做“一般”动物可以做的事儿。
扩展另一个类的语法是:class Child extends Parent
。
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!
在内部,关键字 extends 使用了很好的旧的原型机制进行工作。它将 Rabbit.prototype.[[Prototype]] 设置为 Animal.prototype。所以,如果在 Rabbit.prototype 中找不到一个方法,JavaScript 就会从 Animal.prototype 中获取该方法。
# super 关键字
现在,让我们继续前行并尝试重写一个方法。默认情况下,所有未在 class Rabbit 中指定的方法均从 class Animal 中直接获取。 但是如果我们在 Rabbit 中指定了我们自己的方法,例如 stop(),那么将会使用它
class Rabbit extends Animal {
stop() {
// ……现在这个将会被用作 rabbit.stop()
// 而不是来自于 class Animal 的 stop()
}
}
通常来说,我们不希望完全替换父类的方法,而是希望在父类方法的基础上进行调整或扩展其功能。我们在我们的方法中做一些事儿,但是在它之前或之后或在过程中会调用父类方法。
Class 为此提供了 "super" 关键字
- 执行 super.method(...) 来调用一个父类方法
- 执行 super(...) 来调用一个父类
constructor
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
stop() {
super.stop(); // 调用父类的 stop
this.hide(); // 然后 hide
}
}
let rabbit = new Rabbit("White Rabbit");
WARNING
箭头函数没有 super
class Rabbit extends Animal {
stop() {
setTimeout(() => super.stop(), 1000); // 1 秒后调用父类的 stop
}
}
// 意料之外的 super
setTimeout(function () {
super.stop();
}, 1000);
# 调用父类 constructor
根据 规范,如果一个类扩展了另一个类并且没有 constructor,那么将生成下面这样的“空” constructor:
class Rabbit extends Animal {
// 为没有自己的 constructor 的扩展类生成的
constructor(...args) {
super(...args);
}
}
正如我们所看到的,它调用了父类的 constructor,并传递了所有的参数。
现在,我们给 Rabbit 添加一个自定义的 constructor。除了 name 之外,它还会指定 earLength
class Rabbit extends Animal {
constructor(name, earLength) {
this.speed = 0;
this.name = name;
this.earLength = earLength;
}
// ...
}
// 不工作!
let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
...我们得到了一个报错。现在我们没法新建 rabbit。是什么地方出错了?
简短的解释是:
继承类的 constructor 必须调用 super(...),并且一定要在使用 this 之前调用。
为什么
在 JavaScript 中,继承类(所谓的“派生构造器”,英文为 “derived constructor”)的构造函数与其他函数之间是有区别的。派生构造器具有特殊的内部属性 [[ConstructorKind]]:"derived"
。这是一个特殊的内部标签。
该标签会影响它的 new 行为:
- 当通过 new 执行一个常规函数时,它将创建一个空对象,并将这个空对象赋值给 this。
- 但是当继承的 constructor 执行时,它不会执行此操作。它期望父类的 constructor 来完成这项工作。
因此,派生的 constructor 必须调用 super 才能执行其父类(base)的 constructor,否则 this 指向的那个对象将不会被创建。并且我们会收到一个报错。
为了让 Rabbit 的 constructor 可以工作,它需要在使用 this 之前调用 super(),就像下面这样:
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
# 继承静态属性和方法
静态属性和方法是可被继承的。
例如,下面这段代码中的 Animal.compare 和 Animal.planet 是可被继承的,可以通过 Rabbit.compare 和 Rabbit.planet 来访问:
class Animal {
static planet = "Earth";
constructor(name, speed) {
this.speed = speed;
this.name = name;
}
run(speed = 0) {
this.speed += speed;
alert(`${this.name} runs with speed ${this.speed}.`);
}
static compare(animalA, animalB) {
return animalA.speed - animalB.speed;
}
}
// 继承于 Animal
class Rabbit extends Animal {
hide() {
alert(`${this.name} hides!`);
}
}
let rabbits = [new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5)];
rabbits.sort(Rabbit.compare);
rabbits[0].run(); // Black Rabbit runs with speed 5.
alert(Rabbit.planet); // Earth
它是如何实现的:
结果就是,继承对常规方法和静态方法都有效。
静态方法被用于实现属于整个类的功能。它与具体的类实例无关。
使用继承关键字时,父类的静态属性和方法也会被子类继承。
# 类继承的模板
class SuperType {
constructor(name){
this.name = name;
this.colors = ["red", "green", "blue"];
}
sayName () {}
}
class SubType extends SuperType{
constructor(name, age) {
super()
this.age = age;
}
sayAge () {};
}