JavaScript 中的 super() 是什么?

Avatar of Bailey Jones
Bailey Jones

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

当您看到一些调用 super() 的 JavaScript 代码时,会发生什么?在子类中,您使用 super() 调用其父类的构造函数,并使用 super.<methodName> 访问其父类的方法。本文将假设您至少对构造函数以及子类和父类的概念有所了解。如果这些概念完全陌生,您可能需要从 Mozilla 的关于 面向对象 JavaScript 入门 的文章开始。

Super 并非 JavaScript 独有——包括 Java 和 Python 在内的许多编程语言都具有 super() 关键字,该关键字提供对父类的引用。与 Java 和 Python 不同,JavaScript 不是围绕类继承模型构建的。相反,它扩展了 JavaScript 的原型继承模型,以提供与类继承一致的行为。

让我们进一步了解它并查看一些代码示例。

首先,以下是来自 Mozilla 的类 Web 文档 的一段引用

在 ECMAScript 2015 中引入的 JavaScript 类,主要是 JavaScript 现有基于原型的继承的语法糖。类语法不会为 JavaScript 引入新的面向对象继承模型。

一个简单的子类和父类的示例将有助于说明此引用的真正含义

查看 CodePen 上 Bailey Jones (@bailey_jones) 编写的
ZEzggLK

CodePen 上。

我的示例包含两个类:**鱼** 和 **鳟鱼**。所有鱼都有 **栖息地** 和 **长度** 的信息,因此这些属性属于鱼类。鳟鱼还具有 **品种** 属性,因此它扩展了鱼类以构建其他两个属性。以下是鱼类和鳟鱼的构造函数

class fish {
  constructor(habitat, length) {
    this.habitat = habitat
    this.length = length
  }
}

class trout extends fish {
  constructor(habitat, length, variety) {
    super(habitat, length)
    this.variety = variety
  }
}

鱼类的构造函数定义了栖息地和长度,而鳟鱼的构造函数定义了品种。我必须在鳟鱼的构造函数中调用 super(),否则当我尝试设置 this.variety 时会收到引用错误。这是因为在鳟鱼类的第一行,我告诉 JavaScript 鳟鱼是鱼类的子类,使用了 extends 关键字。这意味着鳟鱼的 this 上下文包括鱼类中定义的属性和方法,加上鳟鱼为自己定义的任何属性和方法。调用 super() 从本质上让 JavaScript 知道鱼是什么,以便它可以为鳟鱼创建一个 this 上下文,其中包含来自鱼的所有内容,以及我们即将为鳟鱼定义的所有内容。鱼类不需要 super(),因为它的“父类”只是 JavaScript 对象。鱼类已经位于原型继承链的顶部,因此调用 super() 没有必要——鱼类的 this 上下文只需要包含 Object,而 JavaScript 已经知道这一点。

鱼类和鳟鱼的原型继承模型以及每个类中 this 上下文中可用的属性。从顶部开始,此处的原型继承链为 Objectfishtrout

我在鳟鱼的构造函数中调用了 super(habitat, length)(引用鱼类),使所有三个属性立即在鳟鱼的 this 上下文中可用。实际上,还有另一种方法可以从鳟鱼的构造函数中获得相同的结果。我必须调用 super() 以避免引用错误,但我不必“正确”地使用鱼类的构造函数期望的参数来调用它。这是因为我不必使用 super() 来为鱼类创建的字段赋值——我只需要确保这些字段存在于鳟鱼的 this 上下文中即可。这是 JavaScript 和真正的类继承模型(如 Java)之间的一个重要区别,在 Java 中,以下代码可能是非法的,具体取决于我如何实现鱼类

class trout extends fish {
  constructor(habitat, length, variety) {
    super()
    this.habitat = habitat
    this.length = length
    this.variety = variety
  }
}

这个备用的鳟鱼构造函数使得难以分辨哪些属性属于鱼类,哪些属性属于鳟鱼类,但它与前面的示例具有相同的结果。唯一的区别是,在这里,使用参数调用 super() 会在当前 this 上下文中创建栖息地和长度属性,而不会为它们分配任何值。如果我在第三行之后调用 console.log(this),它将打印 {habitat: undefined, length: undefined}。第四行和第五行分配值。

我还可以使用 super() 在鳟鱼的构造函数之外引用父类上的方法。在这里,我定义了一个 renderProperties 方法,该方法会将所有类的属性显示到我传递给它的 HTML 元素中。super() 在这里很有用,因为我希望我的鳟鱼类实现一个类似的方法,该方法执行相同操作,并增加一些操作——在更新其 HTML 之前,它会为该元素分配一个类名。我可以通过在相关类函数中调用 super.renderProperties() 来重用鱼类中的逻辑。

class fish {
  renderProperties(element) {
    element.innerHTML = JSON.stringify(this)
  }
}

class trout extends fish {
renderPropertiesWithSuper(element) {
  element.className="green" 
  super.renderProperties(element);
}

您选择的名称很重要。我将鳟鱼类中的方法命名为 renderPropertiesWithSuper(),因为我仍然希望可以选择调用鱼类上定义的 trout.renderProperties()。如果我只是将鳟鱼类中的函数命名为 renderProperties,这将是完全有效的;但是,我将无法再直接从鳟鱼实例访问这两个函数——调用 trout.renderProperties 将调用鳟鱼上定义的函数。这一定是一个有用的实现——对于像这样调用 super 的函数,覆盖其父函数的名称是一个可能更好的模式——但它确实说明了 JavaScript 允许您的类多么灵活。

完全有可能在不使用 super()extends 关键字的情况下实现此示例,这些关键字在前面的代码示例中非常有用,但这很不方便。这就是 Mozilla 所说的“语法糖”。事实上,如果我将我之前的代码插入 Babel 等转译器以确保我的类与旧版本的 JavaScript 兼容,它将生成更接近以下代码的内容。此处的代码大多相同,但您会注意到,如果没有 extendssuper(),我必须将鱼类和鳟鱼定义为函数并直接访问它们的原型。我还必须在第 15、16 和 17 行执行一些额外的连接,以将鳟鱼建立为鱼类的子类,并确保鳟鱼可以在其构造函数中传递正确的 this 上下文。如果您有兴趣深入了解这里发生了什么,Eric Green 撰写了一篇 精彩的帖子,其中包含大量代码示例,介绍了如何使用和不使用 ES2015 构建类。

查看 CodePen 上 Bailey Jones (@bailey_jones) 编写的
wvvdxdd

CodePen 上。

JavaScript 中的类是共享功能的强大方法。例如,React 中的类组件依赖于它们。但是,如果您习惯于在使用类继承模型的其他语言中进行面向对象编程,JavaScript 的行为有时可能会令人惊讶。学习原型继承的基础知识可以帮助您阐明如何在 JavaScript 中使用类。