# 面向对象-类继承

类继承是一个类扩展另一个类的一种方式。

因此,我们可以在现有功能之上创建新功能。

# 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 () {};
}