聊一聊 JavaScript 中的 Super() 是什么?

开发 前端
本文将假定您至少对构造函数以及子类和父类的概念有所了解。如果你不了解这些,则可能需要从Mozilla的Object-oriented JavaScript for beginners开始学习。

当你在js代码中看到有调用super()时,会不会好奇这个super()到底有什么作用?在子类中,您可以使用super()调用其父级的构造函数,并使用super.<方法名>来访问其父级的方法。

本文将假定您至少对构造函数以及子类和父类的概念有所了解。如果你不了解这些,则可能需要从Mozilla的Object-oriented JavaScript for beginners开始学习。

super并不是只javascript语言才有--许多其它编程语言,如java, python都有一个super()关键字来提供对父类的引用。与Java和Python不同,JavaScript并不是围绕类继承模型构建的。相反,它扩展了JavaScript的原型继承模型,以提供与类继承一致的行为。

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

首先,这里引用的一段话Mozilla’s web docs for classes:

JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript’s existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

一个简单的子类和父类的例子将有助于说明这句话的真正含义:

class Fish {
constructor(habitat, length) {
this.habitat = habitat
this.length = length
}
renderProperties(element) {
element.innerHTML = JSON.stringify(this)
}
}
class Trout extends Fish {
constructor(habitat, length, variety) {
super(habitat, length)
this.variety = variety
}
renderPropertiesWithSuper(element) {
element.className="green"
super.renderProperties(element);
}
}
let grouper = new Fish("saltwater", "26in");
console.log(grouper);
grouper.renderProperties(document.getElementById("grouper"));
let rainbowTrout = new Trout("freshwater", "14in", "rainbow");
console.log(rainbowTrout);
//invoke function from parent prototype
rainbowTrout.renderProperties(document.getElementById("rainbowTrout"));
//invoke function from child's prototype
rainbowTrout.renderPropertiesWithSuper(document.getElementById("rainbowTroutParent"));

我的例子有两个类:Fish和 Trout。所有的鱼都有栖息地和长度的信息,所以这些属性属于鱼类。鳟鱼也有一个多样性的属性,所以它基于fish又扩展了属性variety。下面是鱼和鳟鱼的构造函数:

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关键字的“鱼”。

这意味着鳟鱼的上下文包括fish类中定义的属性和方法,以及鳟鱼为其自身定义的任何属性和方法。调用super()本质上使JavaScript知道鱼是什么,以便可以为鳟鱼创建this上下文,其中包括鱼中的所有内容以及我们将为鳟鱼定义的所有内容。fish类不需要super(),因为它的“父级”只是JavaScript对象。Fish已处于原型继承链的顶部,因此无需调用super()。

我在trout的构造函数中调用super(habitat, length),使这三个属性在这个上下文中立即可用。实际上还有另一种方法可以从trout的构造函数中得到相同的行为。我必须调用super()来避免引用错误,但我不必使用fish的构造函数所期望的参数正确调用它。

这是因为我不需要使用super()来给fish创建的字段赋值,我只需要确保这些字段存在于这个上下文上。这是JavaScript与真正的类继承模型(例如Java)之间的重要区别,根据我的实现方式,以下代码可能是非法的:

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

这种替代的trout构造函数使您更难分辨哪些属性属于fish和哪些属性属于trout,但其结果与前面的示例相同。唯一的区别是,在此情况下,不带参数调用super()会在当前此this的上下文上创建属性habitat和length,而无需为其分配任何内容。

如果我在第三行之后调用console.log(this),它将显示{habitat:undefined,length:undefined}。第四行和第五行分配值。

我也可以在trout的构造函数之外使用super(),以引用父类上的方法。在这里,我定义了renderProperties方法,该方法会将类的所有属性显示在我传递给它的HTML元素中。

super()在这里很有用,因为我希望我的trout类实现一个类似的方法,该方法可以完成相同的工作,并且还要多做一些事情—它在更新HTML之前为该元素执定了一个类名。我可以通过在相关类函数内调用super.renderProperties()来重用fish类中的逻辑。

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

你在定义时方法命名很重要。我把我在trout类中的方法叫做renderPropertiesWithSuper(),因为我仍然希望可以选择调用trout.renderProperties(),因为它是在fish类上定义的。

如果我只是将函数命名为trout类中的renderProperties,那将是完全有效的;但是,我将不再能够从trout的实例中直接访问这两个函数--调用trout.renderProperties将调用定义在trout上的函数。

这不一定是一个有用的实现方式--可以说,像这样调用super的函数覆盖其父函数的名称是一个更好的模式--但它确实说明了JavaScript允许你的类是多么灵活。

其实也可以完全可以不使用前面代码示例中非常有用的super()或extends关键字来实现这个例子,只是不太方便。这就是Mozilla所说的 "语法糖 "的意思。事实上,如果我把我之前的代码插入像Babel这样的移植器中,以确保我的类能与旧版本的JavaScript一起工作,它将生成一些更接近下面的代码。

这里的代码大部分是一样的,但你会注意到,如果没有extends和super(),我必须将fish和trout定义为函数并直接访问它们的原型。我还必须在第15、16和17行对原型做一些额外的改动,并确保trout可以在其构造函数中传递正确的this上下文(混合继承)。

如果你有兴趣深入了解这里发生的事情,Eric Green有一篇优秀的帖子,里面有很多代码片段,介绍了如何使用和不使用ES2015构建类以及其继承的关系。

function Fish(habitat, length) {
this.habitat = habitat;
this.length = length;
}
Fish.prototype.renderProperties = function(element) {
element.innerHTML = JSON.stringify(this)
};
function Trout(habitat, length, variety) {
this._super.call(this, habitat, length);
this.variety = variety;
}
Trout.prototype = Object.create(Fish.prototype);
Trout.prototype.constructor = Trout;
Trout.prototype._super = Fish;
Trout.prototype.renderPropertiesWithSuper = function(element) {
element.className="green";
this.renderProperties(element);
};
let grouper = new Fish("saltwater", "26in");
grouper.renderProperties(document.getElementById("grouper"));
var rainbowTrout = new Trout("freshwater", "14in", "rainbow");
//invoke function from parent
rainbowTrout.renderProperties(document.getElementById("rainbowTrout"));
//invoke function from child
rainbowTrout.renderPropertiesWithSuper(document.getElementById("rainbowTroutParent"));

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

责任编辑:姜华 来源: 新钛云服
相关推荐

2020-12-11 11:11:44

原子类JavaCAS

2022-08-30 07:39:57

C++namespace隔离

2019-12-02 16:23:03

Python编程语言“垃圾”回收

2024-01-02 13:26:39

TLSC#线程

2021-01-04 08:09:07

Linux内核Watchdog

2022-01-11 15:44:15

JavaScript图表库数据

2021-12-29 07:18:20

重构工具资源

2022-03-06 20:35:41

并发串行CAP

2021-06-30 07:19:35

微服务业务MySQL

2019-12-12 14:52:10

数据库脚本

2023-07-06 13:56:14

微软Skype

2020-09-08 06:54:29

Java Gradle语言

2018-07-23 15:28:29

HTTPCookieHeader

2024-03-28 09:02:25

PythonGetattr工具

2023-09-22 17:36:37

2021-01-28 22:31:33

分组密码算法

2020-05-22 08:16:07

PONGPONXG-PON

2018-06-07 13:17:12

契约测试单元测试API测试

2021-08-01 09:55:57

Netty时间轮中间件

2023-09-27 16:39:38

点赞
收藏

51CTO技术栈公众号