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 Rabbiterstellen.,

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 Animalerbt:

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

Jeder Ausdruck ist nach 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 Rabbiteinen 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 Sie this.

…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 es thiszu.
  • 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

Erweiterte 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 (*) animalan.,

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,]

Erweiterte Informationen

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 thisaufgerufen 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

  1. Um eine Klasse zu erweitern: class Child extends Parent:
    • Das bedeutet Child.prototype.__proto__ wird Parent.prototype, also werden Methoden vererbt.
  2. Beim Überschreiben eines Konstruktors:
    • müssen wir den übergeordneten Konstruktor als super() in Child konstruktor aufrufen, bevor this.,
  3. Beim Überschreiben einer anderen Methode:
    • können wir super.method() in einer Child – Methode verwenden, um Parent – Methode aufzurufen.
  4. Interna:
    • Methoden merken sich ihre Klasse / ihr Objekt in der internen] Eigenschaft. So löst super übergeordnete Methoden auf.
    • Daher ist es nicht sicher, eine Methode mit super von einem Objekt in ein anderes zu kopieren.,

Außerdem:

  • Pfeilfunktionen haben keine eigene this oder super, sodass sie transparent in den umgebenden Kontext passen.