ES6 Class 继承与 super
发布日期:2021-05-14 15:47:30 浏览次数:12 分类:精选文章

本文共 3164 字,大约阅读时间需要 10 分钟。

Class 继承与 super

Class 在 JavaScript 中基于原型继承,这与传统的类继承有所不同。每当你使用 class 关键字在 JavaScript 中,继承另一个 class 时,这实际上是通过原型链来实现的。这种方式与原型继承机制一致,是 JavaScript 的一个重要特性。

继承的基本语法

要想让一个 class 继承自另一个 class,可以在 class 的声明语法中指定 extends 和父对象。例如,下面的代码展示了 Rabbit 类继承自 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} stopped.`);  }}class Rabbit extends Animal {  hide() {    alert(`${this.name} hides!`);  }}

在上述代码中,Rabbit 类不仅继承了 Animal 类的 constructor 和方法 run()、stop(),还添加了自己的方法 hide()。这里需要注意的是,每当 class 向上查找方法或属性时,它会沿着原型链一直查找到 Object.prototype。

extends 可以跟表达式

类的 extends 之后不仅可以是另一个 class,还可以是一个表达式。这意味着 class 可以继承的是一个构造函数返回的值,非常适合动态生成父类的情况。例如:

function f(phrase) {  return class {    sayHi() {      alert(phrase);    }  };}class User extends f("Hello") {}new User().sayHi(); // Hello

在这个例子中,User 类继承自由 f("Hello") 生成的类,类中具有 sayHi() 方法,能够显示 "Hello"。

重写一个方法

如果我们想要重写 Rabbit 类的 stop 方法,而不是完全替代,我们可以使用 super.stop() 来调用父类的方法。例如:

class Rabbit extends Animal {  stop() {    super.stop(); // 调用父类的 stop 方法    this.hide();  // 然后隐藏  }}

这样,stop 方法会先执行父类的 stop 方法,再执行 Rabbit 的隐藏操作。

构造函数中的 super

在构造函数中,如果没有显式调用 super,JavaScript会自动为派生类生成一个 constructor,直接调用父类的 constructor。但这并非理想,因为如果 parent 类有自己的逻辑,直接调用会导致问题。因此,我们需要在 constructor 中显式调用 super(...args)来调用父类的构造函数。

例如:

class Rabbit extends Animal {  constructor(name, earLength) {    super(name);    this.earLength = earLength;  }}

这样,Rabbit 的构造函数会首先调用父类的构造函数,这样 name 和 earLength 都会被正确初始化。

super 的实现与 [[HomeObject]]

在深入理解 super 的实现中,发现当调用 super.method() 时,JavaScript 会从当前对象的原型链中查找 method,并传递 this 给方法。然而,这会涉及到一些底层实现,包括 [[HomeObject]] 属性,它用于确保函数在这种情况下正确调用。

在面对长链原型的情况时,[[HomeObject]] 属性非常有用,可以防止无限递归调用。例如,当一个对象的原型链远远延伸时,[[HomeObject]] 会确保最终调用的是正确的原型链中的方法。

静态方法和继承

class 语法还支持静态属性和方法的继承。例如:

class Animal {  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;  }}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.

这样,Rabbit 类可以使用继承的 Animal.compare 方法对 rabbit 数组进行排序。

原生对象的继承

JavaScript 的内置对象如 Array、Date 等可以通过原型链进行继承,尽管它们并不是使用 class 语法定义的。这意味着它们有自己的原型链,而这些原型链是原始的对象原型链的延伸。例如:

console.log(Date.__proto__ === Object.prototype);console.log(Array.__proto__ === Object.prototype);

它们的静态方法和属性的继承遵循标准的原型链机制,而不是类继承。

类与库的最佳实践

在大型应用中,尽量使用 class 来定义可扩展的对象,避免复杂的复合继承模式。通过 class 语法可以清晰地表达继承关系,提高代码的可维护性和可读性。

使用 class 时,最好遵循以下原则:

  • 优先扩展内置类:尽量扩展内置对象如 Array、Date 等,使用 Object.mixin() 来复制属性到对象中。
  • 避免不必要的继承:超类仅仅是因为共用某一段代码而存在时,使用混合继承(类似 JavaScript 的extend或者 embracement)会导致内存泄漏。
  • 关注 this 绑定:在使用 super 时,确保 this 被正确绑定,避免在方法中无法正确获取到对象的原型链。
  • 使用 Symbol.species:对于内置类如 Array、Date 等,可以定义 static Symbol.species 方法,返回具体的构造函数类,以确保结果对象的构造函数是正确的。
  • 通过遵循这些原则,开发者可以在 JavaScript 中实现更高效和可维护的代码结构。这样,继承机制就不再是问题,而是成为代码结构的一部分。

    上一篇:HTML几个标签嵌套问题
    下一篇:jQuery选择器

    发表评论

    最新留言

    能坚持,总会有不一样的收获!
    [***.219.124.196]2025年05月01日 11时24分26秒