Klassenvererbung ist eine Möglichkeit für eine Klasse, eine andere Klasse zu erweitern.
Damit wir neue Funktionen zusätzlich zu den vorhandenen erstellen können.
Das Schlüsselwort „extends“
Angenommen, wir haben die Klasse Animal
:
So können wir animal
Objekt und Animal
Klasse grafisch darstellen:
…Und wir möchten eine weitere class Rabbit
erstellen.,
Als kaninchen sind tiere, Rabbit
klasse sollte basierend auf Animal
, haben zugriff auf tier methoden, so dass kaninchen tun können, was“ generische “ tiere tun können.
Die Syntax zum Erweitern einer anderen Klasse lautet: class Child extends Parent
.
Erstellen wir class Rabbit
, das von Animal
erbt:
Objekt von Rabbit
Klasse haben sowohl Zugriff auf Rabbit
Methoden wie rabbit.hide()
als auch auf Animal
Methoden, wie rabbit.run()
.,
Intern funktioniert das Schlüsselwortextends
mit der guten alten Prototypenmechanik. Es setzt Rabbit.prototype.]
auf Animal.prototype
. Wenn also in Rabbit.prototype
keine Methode gefunden wird, nimmt JavaScript sie von Animal.prototype
.,
Zum Beispiel zu finden rabbit.run
Methode, die Engine überprüft (Bottom-up auf dem Bild):
Wie wir uns aus dem Kapitel Native Prototypen erinnern können, verwendet JavaScript selbst Prototypenvererbung für integrierte Objekte. ZB Date.prototype.]
ist Object.prototype
. Aus diesem Grund haben Daten Zugriff auf generische Objektmethoden.,
extends
Die Klassensyntax erlaubt es, nicht nur eine Klasse anzugeben, sondern jeden Ausdruck nach extends
.
Zum Beispiel ein Funktionsaufruf, der die übergeordnete Klasse generiert:
function f(phrase) { return class { sayHi() { alert(phrase); } };}class User extends f("Hello") {}new User().sayHi(); // Hello
Hier erbt class User
vom Ergebnis von f("Hello")
.,
Das kann für fortgeschrittene Programmiermuster nützlich sein, wenn wir Funktionen verwenden, um Klassen abhängig von vielen Bedingungen zu generieren und von ihnen erben können.
Überschreiben einer Methode
Nun gehen wir weiter und überschreiben eine Methode. Standardmäßig werden alle Methoden, die nicht in class Rabbit
angegeben sind, direkt „as is“ von class Animal
übernommen.,
Wenn wir jedoch unsere eigene Methode in Rabbit
angeben, z. B. stop()
, wird sie stattdessen verwendet:
class Rabbit extends Animal { stop() { // ...now this will be used for rabbit.stop() // instead of stop() from class Animal }}
Normalerweise möchten wir eine übergeordnete Methode nicht vollständig ersetzen, sondern darauf aufbauen, um sie zu optimieren oder erweitern Sie seine Funktionalität. Wir machen etwas in unserer Methode, rufen aber die übergeordnete Methode vor/nach oder im Prozess auf.
Klassen bieten "super"
Schlüsselwort dafür.
-
super.method(...)
um eine übergeordnete Methode aufzurufen., -
super(...)
um einen übergeordneten Konstruktor aufzurufen (nur in unserem Konstruktor).
Lassen Sie unser Kaninchen zum Beispiel beim Stoppen automatisch laufen:
Jetzt hat Rabbit
die stop
– Methode, die die übergeordnete super.stop()
aufruft.
Überschreiben Konstruktor
Mit Konstruktoren wird es ein wenig schwierig.
Bisher hatte Rabbit
keine eigene constructor
.,
Wenn eine Klasse gemäß der Spezifikation eine andere Klasse erweitert und keine constructor
, wird die folgende „leere“ constructor
generiert:
class Rabbit extends Animal { // generated for extending classes without own constructors constructor(...args) { super(...args); }}
Wie wir sehen können, ruft sie grundsätzlich die übergeordnete constructor
Übergeben Sie alle Argumente. Das passiert, wenn wir keinen eigenen Konstruktor schreiben.
Fügen wir nun Rabbit
einen benutzerdefinierten Konstruktor hinzu. Es wird die earLength
zusätzlich zu name
:
Whoops!, Wir haben einen Fehler. Jetzt können wir keine Kaninchen schaffen. Was ist schief gelaufen?
Die kurze Antwort lautet:
- Konstruktoren in Erbklassen müssen
super(...)
und (!) tun Sie es, bevor Siethis
.
…Aber warum? Was ist hier Los? In der Tat scheint die Anforderung seltsam.
Natürlich gibt es eine Erklärung. Lassen Sie uns ins Detail gehen, damit Sie wirklich verstehen, was los ist.
In JavaScript gibt es einen Unterschied zwischen einer Konstruktorfunktion einer erbenden Klasse (sogenannter „abgeleiteter Konstruktor“) und anderen Funktionen., Ein abgeleiteter Konstruktor hat eine spezielle interne Eigenschaft ]:"derived"
. Das ist ein spezielles internes Etikett.
Dieses Label beeinflusst sein Verhalten mit new
.
- Wenn eine reguläre Funktion mit
new
ausgeführt wird, erstellt sie ein leeres Objekt und weist esthis
zu. - Aber wenn ein abgeleiteter Konstruktor ausgeführt wird, wird dies nicht ausgeführt. Es erwartet, dass der übergeordnete Konstruktor diesen Job ausführt.,
Daher muss ein abgeleiteter Konstruktor super
aufrufen, um seinen übergeordneten (Basis -) Konstruktor auszuführen, andernfalls wird das Objekt für this
nicht erstellt. Und wir werden einen Fehler bekommen.
Damit der Rabbit
Konstruktor funktioniert, muss er super()
aufrufen, bevor this
verwendet wird, wie hier:
Überschreiben von Klassenfeldern: eine knifflige Notiz
Diese Notiz setzt voraus, dass Sie eine bestimmte Erfahrung mit Klassen haben, möglicherweise in anderen Programmiersprachen sprachen.,
Es bietet einen besseren Einblick in die Sprache und erklärt auch das Verhalten, das eine Fehlerquelle sein könnte (aber nicht sehr oft).
Wenn Sie es schwer zu verstehen finden, fahren Sie einfach fort, lesen Sie weiter und kehren Sie einige Zeit später zurück.
Wir können nicht nur Methoden, sondern auch Klassenfelder überschreiben.
Obwohl es ein kniffliges Verhalten gibt, wenn wir auf ein überschriebenes Feld im übergeordneten Konstruktor zugreifen, das sich von den meisten anderen Programmiersprachen unterscheidet.,
Betrachten Sie dieses Beispiel:
Hier erweitert class Rabbit
Animal
und überschreibt name
Feld mit seinem eigenen Wert.
Es gibt keinen eigenen Konstruktor in Rabbit
, daher wird Animal
Konstruktor aufgerufen.
Interessant ist in beiden Fällen: new Animal()
und new Rabbit()
zeigt die alert
in der Zeile (*)
animal
an.,
Mit anderen Worten, der übergeordnete Konstruktor verwendet immer seinen eigenen Feldwert, nicht den überschriebenen.
Was ist seltsam daran?
Wenn es noch nicht klar ist, vergleichen Sie bitte mit Methoden.
Hier ist der gleiche Code, aber anstelle von this.name
Feld rufen wir this.showName()
Methode:
Bitte beachten Sie: Jetzt ist die Ausgabe anders.
Und das erwarten wir natürlich. Wenn der übergeordnete Konstruktor in der abgeleiteten Klasse aufgerufen wird, verwendet er die überschriebene Methode.
…Aber für Klassenfelder ist es nicht so. Wie gesagt, der übergeordnete Konstruktor verwendet immer das übergeordnete Feld.,
Warum gibt es den Unterschied?
Nun, der Grund liegt in der Feldinitialisierungsreihenfolge. Das Klassenfeld wird initialisiert:
- Vor dem Konstruktor für die Basisklasse (der nichts erweitert),
- Unmittelbar nach
super()
für die abgeleitete Klasse.
In unserem Fall ist Rabbit
die abgeleitete Klasse. Es gibt keine constructor()
darin. Wie bereits erwähnt, ist dies dasselbe, als gäbe es einen leeren Konstruktor mit nur super(...args)
.,
Also ruft new Rabbit()
super()
auf und führt so den übergeordneten Konstruktor aus und (gemäß der Regel für abgeleitete Klassen) erst danach werden seine Klassenfelder initialisiert. Zum Zeitpunkt der Ausführung des übergeordneten Konstruktors gibt es noch keine Rabbit
Klassenfelder, deshalb werden Animal
Felder verwendet.
Dieser subtile Unterschied zwischen Feldern und Methoden ist spezifisch für JavaScript
Glücklicherweise zeigt sich dieses Verhalten nur, wenn im übergeordneten Konstruktor ein überschriebenes Feld verwendet wird., Dann kann es schwierig sein zu verstehen, was los ist, also erklären wir es hier.
Wenn es zu einem Problem wird, kann man es mithilfe von Methoden oder Gettern/Settern anstelle von Feldern beheben.
Super: internals,]
Wenn Sie das Tutorial zum ersten Mal lesen, kann dieser Abschnitt übersprungen werden.
Es geht um die internen Mechanismen hinter der Vererbung und super
.
Lassen Sie uns ein wenig tiefer unter die Haube super
. Wir werden einige interessante Dinge auf dem Weg sehen.,
Zunächst einmal ist es nach allem, was wir bisher gelernt haben, unmöglich, dass super
überhaupt funktioniert!
Ja, in der Tat, fragen wir uns, wie es technisch funktionieren sollte? Wenn eine Objektmethode ausgeführt wird, wird das aktuelle Objekt als this
. Wenn wir super.method()
aufrufen, muss die Engine die method
aus dem Prototyp des aktuellen Objekts abrufen. Aber wie?
Die Aufgabe mag einfach erscheinen, ist es aber nicht., Die Engine kennt das aktuelle Objekt this
, sodass sie die übergeordnete method
als this.__proto__.method
. Leider funktioniert eine solche „naive“ Lösung nicht.
Lassen Sie uns das problem zeigen. Verwenden Sie der Einfachheit halber ohne Klassen einfache Objekte.
Sie können diesen Teil überspringen und unten zum Unterabschnitt ]
gehen, wenn Sie die Details nicht kennen möchten. Das wird nicht Schaden. Oder lesen Sie weiter, wenn Sie daran interessiert sind, die Dinge eingehend zu verstehen.
Im folgenden Beispiel rabbit.__proto__ = animal
., Versuchen wir es nun: In rabbit.eat()
rufen wir animal.eat()
mit this.__proto__
:
In der Zeile (*)
nehmen wir eat
aus dem Prototyp (animal
) und rufen Sie es im Kontext des aktuellen Objekts auf. Bitte beachten Sie, dass .call(this)
hier wichtig ist, da eine einfache this.__proto__.eat()
parent eat
im Kontext des Prototyps ausführen würde, nicht das aktuelle Objekt.,
Und im obigen Code funktioniert es tatsächlich wie beabsichtigt: Wir haben die richtige alert
.
Fügen wir nun der Kette ein weiteres Objekt hinzu. Wir werden sehen, wie die Dinge kaputt gehen:
Der Code funktioniert nicht mehr! Wir können den Fehler beim Aufruf von longEar.eat()
.
Es ist vielleicht nicht so offensichtlich, aber wenn wir longEar.eat()
Aufruf verfolgen, können wir sehen, warum. In beiden Zeilen (*)
und (**)
ist der Wert von this
das aktuelle Objekt (longEar
)., Das ist wichtig: Alle Objektmethoden erhalten das aktuelle Objekt als this
, nicht als Prototyp oder so.
Hier ist das Bild von dem, was passiert:
Das problem kann nicht gelöst werden, indem this
allein.
]
Um die Lösung bereitzustellen, fügt JavaScript eine weitere spezielle interne Eigenschaft für Funktionen hinzu: ]
.,
Wenn eine Funktion als Klassen-oder Objektmethode angegeben wird, wird ihre ]
Eigenschaft zu diesem Objekt.
Dann super
verwendet es, um den übergeordneten Prototyp und seine Methoden aufzulösen.
Mal sehen, wie es funktioniert, zuerst mit einfachen Objekten:
Methoden sind nicht „frei“
Wie wir bereits wissen, sind Funktionen im Allgemeinen „frei“, nicht an Objekte in JavaScript gebunden. Sie können also zwischen Objekten kopiert und mit einer anderen this
aufgerufen werden.,
Die Existenz von ]
verstößt gegen dieses Prinzip, da Methoden sich an ihre Objekte erinnern. ]
kann nicht geändert werden, daher ist diese Bindung für immer.
Der einzige Ort in der Sprache, an dem ]
verwendet wird, ist super
. Wenn also eine Methode super
nicht verwendet, können wir sie weiterhin als frei betrachten und zwischen Objekten kopieren. Aber mit super
kann etwas schief gehen.,
Hier ist die Demo eines falschensuper
Ergebnis nach dem Kopieren:
Ein Aufruf vontree.sayHi()
zeigt „Ich bin ein Tier“. Definitiv falsch.
Der Grund ist einfach:
Hier ist das Diagramm, was passiert:
Methoden, keine Funktionseigenschaften
Der Unterschied ist für uns möglicherweise nicht wesentlich, aber für JavaScript wichtig.
Im folgenden Beispiel wird eine nicht-methodische Syntax zum Vergleich verwendet., ]
Eigenschaft ist nicht gesetzt und die Vererbung funktioniert nicht:
Zusammenfassung
- Um eine Klasse zu erweitern:
class Child extends Parent
:- Das bedeutet
Child.prototype.__proto__
wirdParent.prototype
, also werden Methoden vererbt.
- Das bedeutet
- Beim Überschreiben eines Konstruktors:
- müssen wir den übergeordneten Konstruktor als
super()
inChild
konstruktor aufrufen, bevorthis
.,
- müssen wir den übergeordneten Konstruktor als
- Beim Überschreiben einer anderen Methode:
- können wir
super.method()
in einerChild
– Methode verwenden, umParent
– Methode aufzurufen.
- können wir
- Interna:
- Methoden merken sich ihre Klasse / ihr Objekt in der internen
]
Eigenschaft. So löstsuper
übergeordnete Methoden auf. - Daher ist es nicht sicher, eine Methode mit
super
von einem Objekt in ein anderes zu kopieren.,
- Methoden merken sich ihre Klasse / ihr Objekt in der internen
Außerdem:
- Pfeilfunktionen haben keine eigene
this
odersuper
, sodass sie transparent in den umgebenden Kontext passen.
Schreibe einen Kommentar