JavaScript中的 super() 是什么?
当你在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中的类。
*原文链接:https://css-tricks.com/what-is-super-in-javascript/
文章转载自公众号:新钛云服