Introduction to ES6 Class inheritance and super

不言
Release: 2018-07-09 10:47:32
Original
1935 people have browsed it

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 specifyextendsand the parent object before{..}.

ThisRabbitinherits fromAnimal:

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!
Copy after login

As you can see, as you think,extendkeywords In fact,[Prototype]]is added toRabbit.prototypeand referenced toAnimal.prototype.

Introduction to ES6 Class inheritance and super

So nowrabbitcan access both its own methods andAnimal's methods.

extendscan 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
Copy after login

In the example,class Userinherits 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,Rabbitinherits thestopmethod fromAnimal,this.speed = 0.

If we specify our ownstopinRabbit, it will be used first:

class Rabbit extends Animal { stop() { // ...this will be used for rabbit.stop() } }
Copy after login

...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 thesuperkeyword for this purpose.

  • Usesuper.method(...)to call the parent method.

  • Usesuper(...)to call the parent constructor (only in the constructor function).

For example, to have the rabbit automatically hide whenstop:

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!
Copy after login

Now,Rabbit’sstopMethod calls the method of the parent class throughsuper.stop().

Arrow functions have nosuper

As mentioned in the arrow-functions chapter, arrow functions have nosuper.

It will getsuperfrom the external function. For example:

class Rabbit extends Animal { stop() { setTimeout(() => super.stop(), 1000); // call parent stop after 1sec } }
Copy after login

Thesuperin the arrow function is the same as thestop(), so it works as expected. If we use a normal function here, we will get an error:

// Unexpected super setTimeout(function() { super.stop() }, 1000);
Copy after login

Overriding the constructor

For constructors, this is a bit tricky.

Until now,Rabbithas not had its ownconstructor.
Till now,Rabbitdid not have its ownconstructor.

According to the specification, if a class extends another class and does not have aconstructor, then the followingconstructorwill be automatically generated:

class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); } }
Copy after login

We can see that it calls the parentconstructorpassing all parameters. This will happen if we don't write the constructor ourselves.

Now we will add a custom constructor toRabbit. In addition toname, we will also setearLength:

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.
Copy after login

Oops, something went wrong! Now we can't spawn rabbits, why?

In simple words: the constructor in the inherited class must callsuper(...),(!) and execute it before usingthis.

...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 callsuperotherwise the object withthiswill not be created , and report an error.

ForRabbit, we need to callsuper()before usingthis, 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
Copy after login

Super's implementation and [[HomeObject]]

Let's further understand the underlying implementation ofsuper, 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.
Copy after login

(*)这一行,我们从原型(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
Copy after login

噢,完蛋!调用longEar.eat()报错了!

这原因一眼可能看不透,但如果我们跟踪longEar.eat()调用,大概就知道为什么了。在(*)(**)两行中,this的值是当前对象(longEar)。重点来了:所有方法都将当前对象作为this,而不是原型或其他东西。

因此,在两行(*)(**)中,this.__ proto__的值都是rabbit。他们都调用了rabbit.eat,于是就这么无限循环下去。

情况如图:

Introduction to ES6 Class inheritance and super

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);
Copy after login

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);
Copy after login
  1. ...因此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.
Copy after login

每个方法都会在内部[[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]])
Copy after login

静态方法和继承

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.
Copy after login

现在我们可以调用Rabbit.compare,假设继承的Animal.compare将被调用。

它是如何工作的?再次使用原型。正如你猜到的那样,extends 同样给Rabbit提供了引用到Animal[Prototype]

Introduction to ES6 Class inheritance and super

所以,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);
Copy after login

这样Rabbit可以访问Animal的所有静态方法。

在内置对象中没有静态继承

请注意,内置类没有静态[[Prototype]]引用。例如,Object具有Object.definePropertyObject.keys等方法,但ArrayDate不会继承它们。

DateObject的结构:

Introduction to ES6 Class inheritance and super

DateObject之间毫无关联,他们独立存在,不过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
Copy after login

请注意一件非常有趣的事情。像filtermap和其他内置方法 - 返回新的继承类型的对象。他们依靠constructor属性来做到这一点。

在上面的例子中,

arr.constructor === PowerArray
Copy after login

所以当调用arr.filter()时,它自动创建新的结果数组,就像new PowerArray一样,于是我们可以继续使用 PowerArray 的方法。

我们甚至可以自定义这种行为。如果存在静态 getterSymbol.species,返回新建对象使用的 constructor。

下面的例子中,由于Symbol.species的存在,mapfilter等内置方法将返回普通的数组:

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
Copy after login

我们可以在其他 key 使用Symbol.species,可以用于剥离结果值中的无用方法,或是增加其他方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

用Node编写RESTful API接口

async/await 并行请求和错误处理

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!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!