This article mainly introduces the introduction of ES6 Class inheritance and super. It has certain reference value. Now I share it with everyone. Friends in need can refer to it
Class inheritance and super
class can extend from another class. It's a nice syntax, technically based on prototypal inheritance.
To inherit an object, you need to specify extends
and the parent object before {..}
.
This Rabbit
inherits from Animal
:
class Animal { constructor(name) { this.speed = 0; this.name = name; } run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; alert(`${this.name} stopped.`); } } // Inherit from Animal class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides!
As you can see, as you think, extend
keywords In fact, [Prototype]]
is added to Rabbit.prototype
and referenced to Animal.prototype
.
So now rabbit
can access both its own methods and Animal
's methods.
extends
can be followed by an expression
Class syntax `extends' is not limited to specifying a class, but can also be an expression.
For example, a function that generates a parent class:
function f(phrase) { return class { sayHi() { alert(phrase) } } } class User extends f("Hello") {} new User().sayHi(); // Hello
In the example, class User
inherits the result returned by f('Hello').
For advanced programming patterns, this is useful when we use classes that are generated using functions based on many conditions.
Overriding a method
Now let’s move to the next step and override a method. So far, Rabbit
inherits the stop
method from Animal
, this.speed = 0
.
If we specify our own stop
in Rabbit
, it will be used first:
class Rabbit extends Animal { stop() { // ...this will be used for rabbit.stop() } }
...but usually We don't want to completely replace the parent method, but rather adapt or extend its functionality based on the parent method. We do something so that it calls the parent method before/after or within the procedure.
Class provides the super
keyword for this purpose.
Use
super.method(...)
to call the parent method.Use
super(...)
to call the parent constructor (only in the constructor function).
For example, to have the rabbit automatically hide when stop
:
class Animal { constructor(name) { this.speed = 0; this.name = name; } run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; alert(`${this.name} stopped.`); } } class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } stop() { super.stop(); // call parent stop this.hide(); // and then hide } } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. rabbit.stop(); // White Rabbit stopped. White rabbit hides!
Now, Rabbit
’s stop
Method calls the method of the parent class through super.stop()
.
Arrow functions have no super
As mentioned in the arrow-functions chapter, arrow functions have no super
.
It will get super
from the external function. For example:
class Rabbit extends Animal { stop() { setTimeout(() => super.stop(), 1000); // call parent stop after 1sec } }
The super
in the arrow function is the same as the stop()
, so it works as expected. If we use a normal function here, we will get an error:
// Unexpected super setTimeout(function() { super.stop() }, 1000);
Overriding the constructor
For constructors, this is a bit tricky.
Until now, Rabbit
has not had its own constructor
.
Till now, Rabbit
did not have its own constructor
.
According to the specification, if a class extends another class and does not have a constructor
, then the following constructor
will be automatically generated:
class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); } }
We can see that it calls the parent constructor
passing all parameters. This will happen if we don't write the constructor ourselves.
Now we will add a custom constructor to Rabbit
. In addition to name
, we will also set earLength
:
class Animal { constructor(name) { this.speed = 0; this.name = name; } // ... } class Rabbit extends Animal { constructor(name, earLength) { this.speed = 0; this.name = name; this.earLength = earLength; } // ... } // Doesn't work! let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
Oops, something went wrong! Now we can't spawn rabbits, why?
In simple words: the constructor in the inherited class must call super(...)
,(!) and execute it before using this
.
...but why? What's happening here? Hmm...this request does seem strange.
Now let’s discuss the details so that you can truly understand why -
In JavaScript, constructors that inherit other classes are special. In inherited classes, the corresponding constructor is marked with the special internal attribute [[ConstructorKind]]: "derived"
.
The difference is:
When a normal constructor runs, it creates an empty object as this and then continues running.
But when the derived constructor runs, unlike the above, it looks to the parent constructor to do the work.
So if we are constructing our own constructor then we must call super
otherwise the object with this
will not be created , and report an error.
For Rabbit
, we need to call super()
before using this
, as follows:
class Animal { constructor(name) { this.speed = 0; this.name = name; } // ... } class Rabbit extends Animal { constructor(name, earLength) { super(name); this.earLength = earLength; } // ... } // now fine let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10
Super's implementation and [[HomeObject]]
Let's further understand the underlying implementation of super
, we will see some interesting things.
The first thing to say is that with what we have learned so far, implementing super is impossible.
那么思考一下,这是什么原理?当一个对象方法运行时,它将当前对象作为 this
。如果我们调用 super.method()
,那么如何检索 method
?很容易想到,我们需要从当前对象的原型中取出 method
。从技术上讲,我们(或JavaScript引擎)可以做到这一点吗?
也许我们可以从 this
的 [[Prototype]] 中获得方法,就像 this .__ proto __.method
一样?不幸的是,这是行不通的。
让我们试一试,简单起见,我们不使用 class 了,直接使用普通对象。
在这里,rabbit.eat()
调用父对象的 animal.eat()
方法:
let animal = { name: "Animal", eat() { alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, name: "Rabbit", eat() { // that's how super.eat() could presumably work this.__proto__.eat.call(this); // (*) } }; rabbit.eat(); // Rabbit eats.
在 (*)
这一行,我们从原型(animal
)中取出 eat
,并以当前对象的上下文中调用它。请注意,.call(this)
在这里很重要,因为只写 this .__ proto __.eat()
的话 eat
的调用对象将会是 animal
,而不是当前对象。
以上代码的 alert
是正确的。
但是现在让我们再添加一个对象到原型链中,就要出事了:
let animal = { name: "Animal", eat() { alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, eat() { // ...bounce around rabbit-style and call parent (animal) method this.__proto__.eat.call(this); // (*) } }; let longEar = { __proto__: rabbit, eat() { // ...do something with long ears and call parent (rabbit) method this.__proto__.eat.call(this); // (**) } }; longEar.eat(); // Error: Maximum call stack size exceeded
噢,完蛋!调用 longEar.eat()
报错了!
这原因一眼可能看不透,但如果我们跟踪 longEar.eat()
调用,大概就知道为什么了。在 (*)
和 (**)
两行中, this
的值是当前对象(longEar
)。重点来了:所有方法都将当前对象作为 this
,而不是原型或其他东西。
因此,在两行 (*)
和 (**)
中,this.__ proto__
的值都是 rabbit
。他们都调用了 rabbit.eat
,于是就这么无限循环下去。
情况如图:
1.在 longEar.eat()
里面,(**)
行中调用了 rabbit.eat
,并且this = longEar
。
// inside longEar.eat() we have this = longEar this.__proto__.eat.call(this) // (**) // becomes longEar.__proto__.eat.call(this) // that is rabbit.eat.call(this);
2.然后在rabbit.eat
的 (*)
行中,我们希望传到原型链的下一层,但是 this = longEar
,所以 this .__ proto __.eat
又是 rabbit.eat
!
// inside rabbit.eat() we also have this = longEar this.__proto__.eat.call(this) // (*) // becomes longEar.__proto__.eat.call(this) // or (again) rabbit.eat.call(this);
...因此
rabbit.eat
在无尽循环调动,无法进入下一层。
这个问题不能简单使用 this
解决。
[[HomeObject]]
为了提供解决方案,JavaScript 为函数添加了一个特殊的内部属性:[[HomeObject]]
。
当函数被指定为类或对象方法时,其 [[HomeObject]]
属性为该对象。
这实际上违反了 unbind 函数的思想,因为方法记住了它们的对象。并且 [[HomeObject]]
不能被改变,所以这是永久 bind(绑定)。所以在 JavaScript 这是一个很大的变化。
但是这种改变是安全的。 [[HomeObject]]
仅用于在 super
中获取下一层原型。所以它不会破坏兼容性。
让我们来看看它是如何在 super
中运作的:
let animal = { name: "Animal", eat() { // [[HomeObject]] == animal alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, name: "Rabbit", eat() { // [[HomeObject]] == rabbit super.eat(); } }; let longEar = { __proto__: rabbit, name: "Long Ear", eat() { // [[HomeObject]] == longEar super.eat(); } }; longEar.eat(); // Long Ear eats.
每个方法都会在内部 [[HomeObject]]
属性中记住它的对象。然后 super
使用它来解析原型。
在类和普通对象中定义的方法中都定义了 [[HomeObject]]
,但是对于对象,必须使用:method()
而不是 "method: function()"
。
在下面的例子中,使用非方法语法(non-method syntax)进行比较。这么做没有设置 [[HomeObject]]
属性,继承也不起作用:
let animal = { eat: function() { // should be the short syntax: eat() {...} // ... } }; let rabbit = { __proto__: animal, eat: function() { super.eat(); } }; rabbit.eat(); // Error calling super (because there's no [[HomeObject]])
静态方法和继承
class
语法也支持静态属性的继承。
例如:
class Animal { constructor(name, speed) { this.speed = speed; this.name = name; } run(speed = 0) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } static compare(animalA, animalB) { return animalA.speed - animalB.speed; } } // Inherit from Animal class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } } let rabbits = [ new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5) ]; rabbits.sort(Rabbit.compare); rabbits[0].run(); // Black Rabbit runs with speed 5.
现在我们可以调用 Rabbit.compare
,假设继承的 Animal.compare
将被调用。
它是如何工作的?再次使用原型。正如你猜到的那样,extends 同样给 Rabbit
提供了引用到 Animal
的 [Prototype]
。
所以,Rabbit
函数现在继承 Animal
函数。Animal
自带引用到 Function.prototype
的 [[Prototype]]
(因为它不 extend
其他类)。
看看这里:
class Animal {} class Rabbit extends Animal {} // for static propertites and methods alert(Rabbit.__proto__ === Animal); // true // and the next step is Function.prototype alert(Animal.__proto__ === Function.prototype); // true // that's in addition to the "normal" prototype chain for object methods alert(Rabbit.prototype.__proto__ === Animal.prototype);
这样 Rabbit
可以访问 Animal
的所有静态方法。
在内置对象中没有静态继承
请注意,内置类没有静态 [[Prototype]]
引用。例如,Object
具有 Object.defineProperty
,Object.keys
等方法,但 Array
,Date
不会继承它们。
Date
和 Object
的结构:
Date
和 Object
之间毫无关联,他们独立存在,不过 Date.prototype
继承于 Object.prototype
,仅此而已。
造成这个情况是因为 JavaScript 在设计初期没有考虑使用 class 语法和继承静态方法。
原生拓展
Array,Map 等内置类也可以扩展。
举个例子,PowerArray
继承自原生 Array
:
// add one more method to it (can do more) class PowerArray extends Array { isEmpty() { return this.length === 0; } } let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false let filteredArr = arr.filter(item => item >= 10); alert(filteredArr); // 10, 50 alert(filteredArr.isEmpty()); // false
请注意一件非常有趣的事情。像 filter
,map
和其他内置方法 - 返回新的继承类型的对象。他们依靠 constructor
属性来做到这一点。
在上面的例子中,
arr.constructor === PowerArray
所以当调用 arr.filter()
时,它自动创建新的结果数组,就像 new PowerArray
一样,于是我们可以继续使用 PowerArray 的方法。
我们甚至可以自定义这种行为。如果存在静态 getter Symbol.species
,返回新建对象使用的 constructor。
下面的例子中,由于 Symbol.species
的存在,map
,filter
等内置方法将返回普通的数组:
class PowerArray extends Array { isEmpty() { return this.length === 0; } // built-in methods will use this as the constructor static get [Symbol.species]() { return Array; } } let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false // filter creates new array using arr.constructor[Symbol.species] as constructor let filteredArr = arr.filter(item => item >= 10); // filteredArr is not PowerArray, but Array alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
我们可以在其他 key 使用 Symbol.species
,可以用于剥离结果值中的无用方法,或是增加其他方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!
相关推荐:
The above is the detailed content of Introduction to ES6 Class inheritance and super. For more information, please follow other related articles on the PHP Chinese website!

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

Python and JavaScript have their own advantages and disadvantages in terms of community, libraries and resources. 1) The Python community is friendly and suitable for beginners, but the front-end development resources are not as rich as JavaScript. 2) Python is powerful in data science and machine learning libraries, while JavaScript is better in front-end development libraries and frameworks. 3) Both have rich learning resources, but Python is suitable for starting with official documents, while JavaScript is better with MDNWebDocs. The choice should be based on project needs and personal interests.

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

JavaScript's applications in the real world include server-side programming, mobile application development and Internet of Things control: 1. Server-side programming is realized through Node.js, suitable for high concurrent request processing. 2. Mobile application development is carried out through ReactNative and supports cross-platform deployment. 3. Used for IoT device control through Johnny-Five library, suitable for hardware interaction.

I built a functional multi-tenant SaaS application (an EdTech app) with your everyday tech tool and you can do the same. First, what’s a multi-tenant SaaS application? Multi-tenant SaaS applications let you serve multiple customers from a sing

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

SublimeText3 Chinese version
Chinese version, very easy to use

ZendStudio 13.5.1 Mac
Powerful PHP integrated development environment

Zend Studio 13.0.1
Powerful PHP integrated development environment

MinGW - Minimalist GNU for Windows
This project is in the process of being migrated to osdn.net/projects/mingw, you can continue to follow us there. MinGW: A native Windows port of the GNU Compiler Collection (GCC), freely distributable import libraries and header files for building native Windows applications; includes extensions to the MSVC runtime to support C99 functionality. All MinGW software can run on 64-bit Windows platforms.

Atom editor mac version download
The most popular open source editor