【现代 JS】聊聊 class 中的类继承

lxf2023-05-09 00:47:55

类继承是一个类扩展另一个类的一种方式,我们可以在现有功能之上创建新功能。

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为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!