source:https://css-tricks.com/what-is-super-in-javascript/
Author:Bailey Jone
Published:Nov 6, 2019
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.
<!--HTML代碼-->
<h4>grouper</h4>
<div id="grouper"></div>
<h4>rainbow trout</h4>
<div id="rainbowTrout"></div>
<div id="rainbowTroutParent"></div>
/*CSS樣式*/
.green {
color: green;
}
/*js代碼*/
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"));My example has two classes: fish and trout. All fish have information for habitat and length, so those properties belong to the fish class. Trout also has a property for variety, so it extends fish to build on top of the other two properties. Here are the constructors for fish and trout:
/*js代碼*/
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
}
}The fish class’s constructor defines habitat and length, and the trout’s constructor defines variety. I have to call super() in trout’s constructor, or I』ll get a reference error when I try to set this.variety. That’s because on line one of the trout class, I told JavaScript that trout is a child of fish using the extends keyword. That means trout’s this context includes the properties and methods defined in the fish class, plus whatever properties and methods trout defines for itself. Calling super() essentially lets JavaScript know what a fish is so that it can create a this context for trout that includes everything from fish, plus everything we’re about to define for trout. The fish class doesn’t need super() because its 「parent」 is just the JavaScript Object. Fish is already at the top of the prototypal inheritance chain, so calling super() is not necessary — fish’s this context only needs to include Object, which JavaScript already knows about.
The prototypal inheritance model for fish and trout and the properties available on the this context for each of them. Starting from the top, the prototypal inheritance chain here goes Object → fish → trout.
I called super(habitat, length) in trout’s constructor (referencing the fish class), making all three properties immediately available on the this context for trout. There’s actually another way to get the same behavior out of trout’s constructor. I must call super() to avoid a reference error, but I don’t have to call it 「correctly」 with parameters that fish’s constructor expects. That’s because I don’t have to use super() to assign values to the fields that fish creates — I just have to make sure that those fields exist on trout’s this context. This is an important difference between JavaScript and a true class inheritance model, like Java, where the following code could be illegal depending on how I implemented the fish class:
/*js代碼*/
class trout extends fish {
constructor(habitat, length, variety) {
super()
this.habitat = habitat
this.length = length
this.variety = variety
}
}
This alternate trout constructor makes it harder to tell which properties belong to fish and which belong to trout, but it gets the same result as the previous example. The only difference is that here, calling super() with no parameters creates the properties habitat and length on the current this context without assigning anything to them. If I called console.log(this) after line three, it would print {habitat: undefined, length: undefined}. Lines four and five assign values.I can also use super() outside of trout’s constructor in order to reference methods on the parent class. Here I』ve defined a renderProperties method that will display all the class’s properties into the HTML element I pass to it. super() is useful here because I want my trout class to implement a similar method that does the same thing plus a little more — it assigns a class name to the element before updating its HTML. I can reuse the logic from the fish class by calling super.renderProperties() inside the relevant class function.
/*js代碼*/
class fish {
renderProperties(element) {
element.innerHTML = JSON.stringify(this)
}
}
class trout extends fish {
renderPropertiesWithSuper(element) {
element.className="green"
super.renderProperties(element);
}
The name you choose is important. I』ve called my method in the trout class renderPropertiesWithSuper() because I still want to have the option of calling trout.renderProperties() as it’s defined on the fish class. If I』d just named the function in the trout class renderProperties, that would be perfectly valid; however, I』d no longer be able to access both functions directly from an instance of trout – calling trout.renderProperties would call the function defined on trout. This isn’t necessarily a useful implementation – it’s an arguably better pattern for a function that calls super like this to overwrite its parent function’s name – but it does illustrate how flexible JavaScript allows your classes to be.
It is completely possible to implement this example without the use of the super() or extends keywords that were so helpful in the previous code sample, it’s just much less convenient. That’s what Mozilla meant by 「syntactical sugar.」 In fact, if I plugged my previous code into a transpiler like Babel to make sure my classes worked with older versions of JavaScript, it would generate something closer to the following code. The code here is mostly the same, but you』ll notice that without extends and super(), I have to define fish and trout as functions and access their prototypes directly. I also have to do some extra wiring on lines 15, 16, and 17 to establish trout as a child of fish and to make sure trout can be passed the correct this context in its constructor. If you’re interested in a deep dive into what’s going on here, Eric Green has an excellent post with lots of code samples on how to build classes with and without ES2015./*js代碼-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"));```Classes in JavaScript are a powerful way to share functionality. Class components in React, for example, rely on them. However, if you’re used to Object Oriented programming in another language that uses a class inheritance model, JavaScript’s behavior can occasionally be surprising. Learning the basics of prototypal inheritance can help clarify how to work with classes in JavaScript.
super 這個關鍵字既可以當作函數使用,也可以當作對象使用。在這兩種情況下,它的使用方法完全不同。
該情況下,super作為函數調用時代表父類的構造函數。在ES6標準之下,子類的構造函數必須執行一次super函數,請看演示代碼:
/*js代碼*/
class Family{
constructor(name,age) {
this.name = name;
this.age = age;
}
loveFamily(){
console.log(`我是父類中的方法:We are a big family,and her name is ${this.name},she has been ${this.age} years old,all our members love it deeply.`)
}
}
class Child extends Family{
constructor(name,age,mother) {
super(name,age);
this.mother = mother;
}
loveMom(){
console.log(`我是子類中的方法:My name is ${this.name},i am ${this.age} years old,and this is my mom,her name is ${this.mother}.`);
}
}
let family = new Family("hahaCoder",100)
family.loveFamily()
let child = new Child("shipudong",22,"Hui")
child.loveMom()上述代碼中,我們定義了父類 Family ,子類 Child,並讓子類通過super關鍵字繼承了來自父類的兩個屬性:name和age,如果在子類中我們沒有調用父類的構造函數,即在子類中沒有使用super關鍵字,JS引擎就會報錯:
我們知道,super表示父類 Family 的構造函數,但是當在子類 Child 中使用的時候,其返回的是子類的實例,即其內部this指針指向的是子類Child,因此我們可以這樣理解:
super() === Family.prototype.constructor.call(this)
上述代碼太過冗餘了,我們不看它了,來寫一小段代碼,證實上述的這個結論:
/*js代碼*/
class A {
constructor() {
console.log(`親,當前正在執行的是${new.target.name}函數喲~`)
}
}
class B extends A{
constructor() {
super();
}
}
let a_exp = new A()
let b_exp = new B()
上述代碼中,new.target指向當前正在執行的函數。可以看到,在super()執行時,它指向的是子類B的構造函數,而不是父類A的構造函數,也就是說super()內部的this指向的是B,故上述結論得證。
最後,關於當super作為一個函數使用時的情況,我們在提醒最後一點:super()只能用在子類的構造函數中,用在其他地方會報錯,請看錯誤代碼:
/*js代碼*/
class A {}
class B extends A{
m(){
super();
}
}
該情況下,super作為對象時在普通方法中指向父類的原型對象;在靜態方法中指向父類。
/*js代碼*/
class SuperType {
constructor(name,age) {
this.name = name;
this.age = age;
}
sayHello(){
console.log(`Hello,this is ${this.name},he is ${this.age} years old,nice to meet you guys!`)
}
}
class SubType extends SuperType{
constructor() {
super();
super.sayHello();
console.log(super.name);
}
}
let sub = new SubType()
我們知道,在ES6中如果我們需要在類的原型對象上定義方法,可以直接在class中去寫,不用在像以前這樣:
/*js代碼*/
function Person(){}
Person.prototype.sayName = function(){
/*
邏輯功能
*/
}
因為在ES6標準中,方法是直接定義在原型對象上的,因此子類的實例對象可以藉助原型鏈訪問到子類的原型對象、父類的原型對象上的所有方法,但當我們想要訪問父類實例中的屬性時,卻不能訪問到,這是因為super指向父類的原型對象,定義在父類實例上的方法或屬性無法通過super來調用。上述代碼中通過super關鍵字調用父類的方法這一行代碼可以等價為如下代碼:
/*js代碼*/
class SubType extends SuperType{
constructor() {
super();
SuperType.prototype.sayHello(); // 本行代碼等價於super.sayHello();
}
}
如果我們必須要訪問父類實例上的屬性或方法,那我們就必須做如下定義:
/*js代碼*/
class A{}
A.prototype.x = 2;
class B extends A{
constructor(){
super();
console.log(super.x);
}
}
即把我們想要訪問到的屬性和方法,定義在父類的原型對象上。
在ES6標準下,規定:當通過super()調用父類的方法時,super會被綁定到子類的this,我們來看一段演示代碼:
/*js代碼*/
class SuperType {
constructor() {
this.x = 1;
}
}
class SubType extends SuperType{
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let sub = new SubType()
上述代碼中,由於super會綁定子類的this,因此當通過super對某個屬性賦值,這時super就是this,賦值的屬性會變成子類實例的屬性,故當super.x被賦值為3時,等同於對this.x賦值為3,故當讀取this.x時候,其值為3,當讀取super.x時,由於其父類原型對象上並沒有關於x的任何定義,故其值為undefined。
我們最後再來說一下super在靜態方法中的情況:
首先,我們有必要來說一下class中的靜態方法:我們知道,類相當於實例的原型,所有類中定義的方法都會被實例繼承,如果在一個方法前加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類調用。
接下來, 我們看一段代碼:
/*js代碼*/
class SuperType {
static SuperMethod(info){
console.log(`我是父類中的靜態方法,我被定義在父類中:${info}`)
}
SuperMethod(info){
console.log(`我是父類中的普通方法,我被定義在父類的原型對象上:${info}`)
}
}
class SubType extends SuperType{
static SuperMethod(info){
super.SuperMethod(info)
}
SuperMethod(info){
super.SuperMethod(info)
}
}
SubType.SuperMethod("hello,靜態方法")
let sub = new SubType()
sub.SuperMethod("hello,普通方法")
上述代碼中,super在靜態方法中指向父類,在普通方法中執向父類的原型對象。
最後,在提醒各位小夥伴兩點:
/*js代碼*/
class A {}
class B extends A{
constructor() {
super();
console.log(super);
}
}
/*js代碼*/
let obj = {
sayName(){
console.log( `sayName:${super.toString()}`)
}
}
obj.sayName()
本文我們主要講解了super關鍵字的兩種應用場景,包括在函數中使用和在對象中使用,並嘗試了一種新的寫文方式:中英結合,小夥伴們理解了嗎?快去實現一下吧~