Heim >Web-Frontend >js-Tutorial >Einführung in die ES6-Klassenvererbung und Super
Dieser Artikel stellt hauptsächlich die Einführung von ES6-Klassenvererbung und -Super vor. Er hat einen gewissen Referenzwert. Jetzt kann ich ihn mit allen Freunden teilen, die ihn benötigen 🎜>Die Klasse kann von einer anderen Klasse ausgehen. Es ist eine schöne Syntax, die technisch auf prototypischer Vererbung basiert.
angeben.
Dieses {..}
erbt von extends
:
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!
Wie Sie sehen können, fügt das Schlüsselwort Rabbit
tatsächlich Animal
in
. extend
Rabbit.prototype
[Prototype]]
Animal.prototype
sowohl auf seine eigenen Methoden als auch auf die Methoden von zugreifen. Auf
rabbit
kann ein Ausdruck folgen Animal
extends
Zum Beispiel eine Funktion, die eine übergeordnete Klasse generiert: function f(phrase) { return class { sayHi() { alert(phrase) } } } class User extends f("Hello") {} new User().sayHi(); // Hello
Im Beispiel erbt
das von f('Hello') zurückgegebene Ergebnis. Für fortgeschrittene Programmiermuster ist dies nützlich, wenn wir Klassen verwenden, die mithilfe von Funktionen generiert werden, die auf vielen Bedingungen basieren.class User
Eine Methode überschreiben
Jetzt gehen wir zum nächsten Schritt über und überschreiben eine Methode. Bisher erbt
die. Rabbit
Animal
Wenn wir unser eigenes stop
in this.speed = 0
angeben, wird es zuerst verwendet:
class Rabbit extends Animal { stop() { // ...this will be used for rabbit.stop() } }
... Aber normalerweise wollen wir die übergeordnete Methode nicht vollständig ersetzen, sondern Es dient dazu, seine Funktionen basierend auf der übergeordneten Methode anzupassen oder zu erweitern. Wir tun etwas, damit die übergeordnete Methode vor/nach oder innerhalb der Prozedur aufgerufen wird. Rabbit
stop
Class stellt zu diesem Zweck das Schlüsselwort
super
Verwenden Sie
super.method(...)
Verwenden Sie
super(...)
Lassen Sie das Kaninchen beispielsweise automatisch verstecken, wenn
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!Jetzt ruft die
-Methode von stop
die Methode der übergeordneten Klasse auf bis
Rabbit
Pfeilfunktionen haben kein stop
super.stop()
super
von der externen Funktion erhalten. Beispiel: super
class Rabbit extends Animal { stop() { setTimeout(() => super.stop(), 1000); // call parent stop after 1sec } }Das
in der Pfeilfunktion ist dasselbe wie das darin enthaltene super
, sodass es wie erwartet funktioniert. Wenn wir hier eine normale Funktion verwenden, erhalten wir eine Fehlermeldung:
// Unexpected super setTimeout(function() { super.stop() }, 1000);
Überschreiben des Konstruktorssuper
stop()
Bei Konstruktoren ist das etwas knifflig.
.
Bisher hatte kein eigenes Rabbit
.constructor
Wenn laut Spezifikation eine Klasse eine andere Klasse erweitert und es kein Rabbit
gibt, dann wird das folgende constructor
tun automatisch generiert werden:
class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); } }
Wir können sehen, dass es das übergeordnete Element aufruft constructor
und alle Parameter übergibt. Dies wird passieren, wenn wir den Konstruktor nicht selbst schreiben. constructor
hinzu. Zusätzlich zu constructor
werden wir auch
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.
Rabbit
Ups, da ist etwas schief gelaufen! Jetzt können wir keine Kaninchen mehr züchten, warum? name
earLength
Einfach ausgedrückt: Der Konstruktor in der geerbten Klasse muss (!) aufrufen und ausführen, bevor er verwendet.
...aber warum? Was ist los? Hmm ... diese Bitte kommt mir seltsam vor. super(...)
this
Lassen Sie uns nun die Details besprechen, damit Sie wirklich verstehen, warum –
In JavaScript sind Konstruktoren, die andere Klassen erben, etwas Besonderes. In geerbten Klassen wird der entsprechende Konstruktor als spezielles internes Attribut
markiert. Der Unterschied ist:[[ConstructorKind]]:“derived”
Wenn ein normaler Konstruktor ausgeführt wird, erstellt er ein leeres Objekt wie dieses und läuft weiter.
Aber wenn der abgeleitete Konstruktor ausgeführt wird, erwartet er anders als oben, dass der übergeordnete Konstruktor die Aufgabe erledigt.
Wenn wir also unseren eigenen Konstruktor erstellen, müssen wir
aufrufen, sonst wird das Objekt mitFür super
müssen wir this
aufrufen, bevor wir
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
Rabbit
Die Implementierung von Super ist die gleiche wie [[HomeObject]]this
super()
Lassen Sie uns tiefer in die zugrunde liegende Implementierung von eintauchen und wir werden einige interessante Dinge sehen. Das erste, was ich sagen muss, ist, dass die Umsetzung von Super mit dem, was wir bisher gelernt haben, unmöglich ist.
那么思考一下,这是什么原理?当一个对象方法运行时,它将当前对象作为 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中文网!
相关推荐:
Das obige ist der detaillierte Inhalt vonEinführung in die ES6-Klassenvererbung und Super. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!