What is super() in JavaScript?

简介: 小伙伴们大家好,今天我们来说一下ES6中的super关键字,这次我尝试了一种新的写文形式:中英文结合,在本文的前半部分,我会引用 Bailey Jone 的一篇关于super的文章,后半部分我会结合ES6的标准和实际的小demo对其做一个总结,请看文章。

前言


小伙伴们大家好,今天我们来说一下ES6中的super关键字,这次我尝试了一种新的写文形式:中英文结合,在本文的前半部分,我会引用 Bailey Jone 的一篇关于super的文章,后半部分我会结合ES6的标准和实际的小demo对其做一个总结,请看文章。


source:https://css-tricks.com/what-is-super-in-javascript/

Author:Bailey Jone

Published:Nov 6, 2019


What is super()?


What’s happening when you see some JavaScript that calls super()?.In a child class, you use super() to call its parent’s constructor and super.<methodName> to access its parent’s methods. This article will assume at least a little familiarity with the concepts of constructors and child and parent classes. If that’s totally new, you may want to start with Mozilla’s article on Object-oriented JavaScript for beginners.


Super isn’t unique to Javascript — many programming languages, including Java and Python, have a super() keyword that provides a reference to a parent class. JavaScript, unlike Java and Python, is not built around a class inheritance model. Instead, it extends JavaScript’s prototypal inheritance model to provide behavior that’s consistent with class inheritance.

Let’s learn a bit more about it and look at some code samples.
First off, here’s a quote from 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.


An example of a simple child and parent class will help illustrate what this quote really means:

 

<!--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.

 

微信图片_20220611021252.png


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 Objectfishtrout.



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 这个关键字既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的使用方法完全不同。


01 - 作为函数使用


该情况下,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引擎就会报错:


微信图片_20220611021308.png


我们知道,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();
    }
}


微信图片_20220611021313.png



02 - 作为对象使用


该情况下,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在静态方法中指向父类,在普通方法中执向父类的原型对象。


最后,在提醒各位小伙伴两点:


  • 使用super时,必须显示的指定super是作为函数使用还是作为对象使用


/*js代码*/
class A {}
class B extends A{
    constructor() {
        super();
        console.log(super);
    }
}


微信图片_20220611021318.png


  • 任意一个对象均可使用super关键字,因为对象总是继承于其他对象的


/*js代码*/
let obj = {
    sayName(){
        console.log( `sayName:${super.toString()}`)
    }
}
obj.sayName()



微信图片_20220611021322.png



写在最后


本文我们主要讲解了super关键字的两种应用场景,包括在函数中使用和在对象中使用,并尝试了一种新的写文方式:中英结合,小伙伴们理解了吗?快去实现一下吧~

相关文章
|
12月前
|
监控 数据可视化 项目管理
CM模式是什么?如何应用?
CM 模式(Construction Management)即建设管理模式,起源于20世纪60年代的美国,通过专业的建设管理团队在项目早期介入,优化设计方案,协调各方资源,有效提升项目质量和进度控制。该模式已广泛应用于各类建筑工程,并不断创新发展,适应数字化、绿色建筑及国际化需求。未来,CM模式将继续推动建筑行业的进步。
1335 2
|
运维 监控 安全
DevOps 测试实践指南
软件开发公司一直在采用 DevOps,因为它有助于自动化和简化应用程序的开发生命周期。不仅如此,DevOps 还通过规划、沟通、流程和工具,更好地协调了开发团队和运维团队,从而提高了项目的交付质量和速度。但是测试 DevOps 的最佳策略是什么呢?本文将讨论 DevOps 的基本概念、生命周期、最佳实践以及我们应该使用的工具。
868 0
DevOps 测试实践指南
|
12月前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:一场永无止境的较量
【10月更文挑战第3天】 本文旨在深入剖析安卓 (Android) 和iOS两大移动操作系统在安全性方面的显著差异,通过细致对比,揭示它们在隐私保护、数据加密、应用生态监管以及系统更新机制等关键领域的不同策略与实践。文章不仅从技术层面探讨了两者的设计理念差异,还结合了实际案例分析,展示了这些差异如何影响用户体验和数据安全。最终,旨在为读者提供一个全面的视角,理解在日益复杂的数字环境中,选择何种移动平台可能更符合其对安全性和隐私保护的需求。
|
7月前
「闲置竞价域名」货源上新,操作指南请查收
「闲置竞价域名」货源上新,操作指南请查收
145 3
|
8月前
|
机器学习/深度学习 人工智能 自然语言处理
DeepSeek估值1500亿美元:AI领域的新星崛起
据外媒Bloomberg报道,DeepSeek的估值已达到1500亿美元,相当于OpenAI估值的一半。在巴黎AI峰会上,DeepSeek成为焦点话题,其影响力甚至延伸至普通民众。据报道,巴黎地铁上的70岁老人也在讨论这一新兴科技公司,显示出DeepSeek不仅在专业领域备受关注,在公众中也引起了广泛兴趣。
648 7
|
8月前
|
自然语言处理 运维 Linux
阿里云操作系统智能助手OS Copilot测评报告及建议
阿里云推出的OS Copilot是一款基于大模型构建的操作系统智能助手,旨在通过自然语言处理技术与操作系统经验的深度融合,为Linux用户提供前所未有的使用体验。它具备自然语言问答、辅助命令执行和系统运维调优等核心功能,极大降低了Linux的学习门槛,提升了工作效率。测试显示,OS Copilot在功能、性能、易用性和实用性方面表现出色,能够帮助用户高效解决问题并优化系统性能。未来,期待其持续优化升级,加入更多实用功能,进一步提升用户体验。
236 4
|
物联网 5G
Wi-Fi 7:主要功能、优势和与前代的改进
【8月更文挑战第23天】
1086 0
|
监控 安全 数据挖掘
如何精准监控员工上网:这三款员工上网行为监控告诉你
本文介绍了三款员工上网行为监控软件,以增强企业网络安全性。WorkWin提供USB管理、带宽控制及远程管理,确保资源有效分配和安全。InterGuard专注敏感数据检测、违规行为监控,即时消息审查,保障企业安全。而Hubstaff侧重工时追踪、活动记录和应用使用报告,优化工作效率和团队管理。这些工具旨在平衡安全与效率,助力企业保护资源和提升生产力。
817 3
|
10月前
|
存储 算法 C语言
【C语言】字符常量详解
字符常量是C语言中处理字符数据的重要工具。通过单引号括起一个字符,我们可以方便地使用字符常量进行字符判断、字符运算和字符串处理等操作。理解字符常量的表示方法、使用场景和ASCII码对应关系,对于编写高效的C语言程序至关重要。
751 11
|
11月前
|
人工智能 安全 测试技术
探索AI在软件开发中的应用:提升开发效率与质量
【10月更文挑战第31天】在快速发展的科技时代,人工智能(AI)已成为软件开发领域的重要组成部分。本文探讨了AI在代码生成、缺陷预测、自动化测试、性能优化和CI/CD中的应用,以及这些应用如何提升开发效率和产品质量。同时,文章也讨论了数据隐私、模型可解释性和技术更新等挑战。