当您看到一些调用 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 上下文中可用的属性。从顶部开始,此处的原型继承链为 Object → fish → trout。我在鳟鱼的构造函数中调用了 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 兼容,它将生成更接近以下代码的内容。此处的代码大多相同,但您会注意到,如果没有 extends 和 super(),我必须将鱼类和鳟鱼定义为函数并直接访问它们的原型。我还必须在第 15、16 和 17 行执行一些额外的连接,以将鳟鱼建立为鱼类的子类,并确保鳟鱼可以在其构造函数中传递正确的 this 上下文。如果您有兴趣深入了解这里发生了什么,Eric Green 撰写了一篇 精彩的帖子,其中包含大量代码示例,介绍了如何使用和不使用 ES2015 构建类。
查看 CodePen 上 Bailey Jones (@bailey_jones) 编写的
wvvdxdd。
在 CodePen 上。
JavaScript 中的类是共享功能的强大方法。例如,React 中的类组件依赖于它们。但是,如果您习惯于在使用类继承模型的其他语言中进行面向对象编程,JavaScript 的行为有时可能会令人惊讶。学习原型继承的基础知识可以帮助您阐明如何在 JavaScript 中使用类。
不错的文章。我非常喜欢最后一个示例,您将类和子类转换为原型继承风格的代码。
写得非常棒的文章,继续努力,您之前关于 SVG 中的可点击元素的文章真的帮了我大忙。我也想说,这只是 JavaScript 成为更棒的编码工具的又一步。