Was ist super() in JavaScript?

Avatar of Bailey Jones
Bailey Jones am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Was passiert, wenn Sie JavaScript sehen, das super() aufruft? In einer abgeleiteten Klasse verwenden Sie super(), um den Konstruktor der übergeordneten Klasse aufzurufen, und super.<methodName>, um auf die Methoden der übergeordneten Klasse zuzugreifen. Dieser Artikel setzt ein grundlegendes Verständnis von Konstruktoren sowie von untergeordneten und übergeordneten Klassen voraus. Wenn Ihnen das völlig neu ist, möchten Sie vielleicht Mozillas Artikel über objektorientiertes JavaScript für Anfänger lesen.

Super ist nicht einzigartig für JavaScript – viele Programmiersprachen, darunter Java und Python, verfügen über ein super()-Schlüsselwort, das eine Referenz auf eine übergeordnete Klasse bereitstellt. JavaScript, im Gegensatz zu Java und Python, basiert nicht auf einem Klassen-Vererbungsmodell. Stattdessen erweitert es das prototypenbasierte Vererbungsmodell von JavaScript, um ein Verhalten zu bieten, das mit der Klassenvererbung konsistent ist.

Lassen Sie uns mehr darüber erfahren und einige Codebeispiele betrachten.

Zuerst eine Aussage aus den Web-Dokumenten von Mozilla für Klassen

JavaScript-Klassen, eingeführt in ECMAScript 2015, sind hauptsächlich syntaktischer Zucker über der bestehenden prototypenbasierten Vererbung von JavaScript. Die Klassensyntax führt kein neues objektorientiertes Vererbungsmodell in JavaScript ein.

Ein Beispiel für eine einfache untergeordnete und übergeordnete Klasse veranschaulicht, was diese Aussage wirklich bedeutet

Siehe den Pen
ZEzggLK
von Bailey Jones (@bailey_jones)
auf CodePen.

Mein Beispiel hat zwei Klassen: **fish** und **trout**. Alle Fische haben Informationen über **habitat** (Lebensraum) und **length** (Länge), daher gehören diese Eigenschaften zur Klasse fish. Trout hat zusätzlich eine Eigenschaft für **variety** (Sorte), daher erweitert es fish, um auf den anderen beiden Eigenschaften aufzubauen. Hier sind die Konstruktoren für fish und trout

class fish {
  constructor(habitat, length) {
    this.habitat = habitat
    this.length = length
  }
}

class trout extends fish {
  constructor(habitat, length, variety) {
    super(habitat, length)
    this.variety = variety
  }
}

Der Konstruktor der Klasse fish definiert habitat und length, und der Konstruktor von trout definiert variety. Ich *muss* super() im Konstruktor von trout aufrufen, sonst erhalte ich einen Referenzfehler, wenn ich versuche, this.variety zu setzen. Das liegt daran, dass ich in Zeile eins der trout-Klasse mit dem Schlüsselwort extends JavaScript gesagt habe, dass trout eine Unterklasse von fish ist. Das bedeutet, dass der `this`-Kontext von trout die in der Klasse fish definierten Eigenschaften und Methoden sowie alle Eigenschaften und Methoden enthält, die trout selbst definiert. Das Aufrufen von super() teilt JavaScript im Wesentlichen mit, was ein Fisch ist, damit es einen `this`-Kontext für trout erstellen kann, der alles von fish enthält, plus alles, was wir für trout definieren werden. Die Klasse fish benötigt kein super(), da ihre "Eltern" nur das JavaScript-Objekt sind. Fish steht bereits am Anfang der prototypenbasierten Vererbungskette, daher ist das Aufrufen von super() nicht notwendig – der `this`-Kontext von fish muss nur Object enthalten, was JavaScript bereits kennt.

Das prototypenbasierte Vererbungsmodell für fish und trout und die auf dem this-Kontext verfügbaren Eigenschaften für jede von ihnen. Von oben beginnend, verläuft die prototypenbasierte Vererbungskette hier: Objectfishtrout.

Ich habe super(habitat, length) im Konstruktor von trout (referenzierend auf die fish-Klasse) aufgerufen, wodurch alle drei Eigenschaften sofort auf dem `this`-Kontext für trout verfügbar sind. Es gibt tatsächlich einen weiteren Weg, das gleiche Verhalten aus dem Konstruktor von trout zu erzielen. Ich muss super() aufrufen, um einen Referenzfehler zu vermeiden, aber ich muss es nicht "korrekt" mit Parametern aufrufen, die der Konstruktor von fish erwartet. Das liegt daran, dass ich super() nicht verwenden muss, um Werte für die Felder zuzuweisen, die fish erstellt – ich muss nur sicherstellen, dass diese Felder im `this`-Kontext von trout vorhanden sind. Dies ist ein wichtiger Unterschied zwischen JavaScript und einem echten Klassenvererbungsmodell wie Java, wo der folgende Code illegal sein könnte, je nachdem, wie ich die fish-Klasse implementiert habe

class trout extends fish {
  constructor(habitat, length, variety) {
    super()
    this.habitat = habitat
    this.length = length
    this.variety = variety
  }
}

Dieser alternative trout-Konstruktor macht es schwieriger zu erkennen, welche Eigenschaften zu fish und welche zu trout gehören, aber er liefert das gleiche Ergebnis wie das vorherige Beispiel. Der einzige Unterschied ist, dass hier das Aufrufen von super() ohne Parameter die Eigenschaften habitat und length im aktuellen `this`-Kontext erstellt, ohne ihnen etwas zuzuweisen. Wenn ich nach Zeile drei console.log(this) aufrufen würde, würde es {habitat: undefined, length: undefined} ausgeben. Zeilen vier und fünf weisen Werte zu.

Ich kann super() auch außerhalb des Konstruktors von trout verwenden, um auf Methoden der übergeordneten Klasse zuzugreifen. Hier habe ich eine Methode namens renderProperties definiert, die alle Eigenschaften der Klasse in das HTML-Element anzeigt, das ich ihr übergebe. super() ist hier nützlich, da ich möchte, dass meine trout-Klasse eine ähnliche Methode implementiert, die dasselbe tut, plus ein bisschen mehr – sie weist dem Element eine Klasse zu, bevor sie sein HTML aktualisiert. Ich kann die Logik aus der fish-Klasse wiederverwenden, indem ich super.renderProperties() innerhalb der relevanten Klassenfunktion aufrufe.

class fish {
  renderProperties(element) {
    element.innerHTML = JSON.stringify(this)
  }
}

class trout extends fish {
renderPropertiesWithSuper(element) {
  element.className="green" 
  super.renderProperties(element);
}

Der Name, den Sie wählen, ist wichtig. Ich habe meine Methode in der trout-Klasse renderPropertiesWithSuper() genannt, weil ich immer noch die Option haben möchte, trout.renderProperties() aufzurufen, wie sie in der fish-Klasse definiert ist. Wenn ich die Funktion in der trout-Klasse einfach renderProperties genannt hätte, wäre das vollkommen in Ordnung; jedoch könnte ich dann nicht mehr auf beide Funktionen direkt von einer Instanz von trout zugreifen – das Aufrufen von trout.renderProperties würde die in trout definierte Funktion aufrufen. Das ist nicht unbedingt eine nützliche Implementierung – es ist ein argumentativ besseres Muster für eine Funktion, die super auf diese Weise aufruft, die Funktion ihrer Eltern zu überschreiben –, aber es veranschaulicht, wie flexibel JavaScript Ihre Klassen sein lässt.

Es ist durchaus möglich, dieses Beispiel ohne die Schlüsselwörter super() oder extends zu implementieren, die im vorherigen Codebeispiel so hilfreich waren, es ist nur viel weniger bequem. Das ist es, was Mozilla mit "syntaktischem Zucker" meinte. Tatsächlich würde mein vorheriger Code, wenn ich ihn in einen Transpiler wie Babel einfügen würde, um sicherzustellen, dass meine Klassen mit älteren JavaScript-Versionen funktionieren, etwas Ähnliches wie der folgende Code generieren. Der Code hier ist weitgehend gleich, aber Sie werden feststellen, dass ich ohne extends und super() fish und trout als Funktionen definieren und direkt auf ihre Prototypen zugreifen muss. Ich muss auch einige zusätzliche Verdrahtungen in den Zeilen 15, 16 und 17 vornehmen, um trout als Unterklasse von fish zu etablieren und sicherzustellen, dass trout im Konstruktor den richtigen `this`-Kontext übergeben werden kann. Wenn Sie daran interessiert sind, tief in das einzutauchen, was hier vor sich geht, hat Eric Green einen exzellenten Beitrag mit vielen Codebeispielen, wie man Klassen mit und ohne ES2015 erstellt.

Siehe den Pen
wvvdxdd
von Bailey Jones (@bailey_jones)
auf CodePen.

Klassen in JavaScript sind eine leistungsstarke Möglichkeit, Funktionalität zu teilen. Klassenkomponenten in React beispielsweise basieren auf ihnen. Wenn Sie jedoch die objektorientierte Programmierung in einer anderen Sprache gewohnt sind, die ein Klassenvererbungsmodell verwendet, kann das Verhalten von JavaScript gelegentlich überraschend sein. Das Erlernen der Grundlagen der prototypenbasierten Vererbung kann Ihnen helfen, die Arbeit mit Klassen in JavaScript zu verstehen.