Abschnitt 18 · Lektion 2
Vererbung
Zwei Klassen, die einander ähneln, und du willst den gemeinsamen Teil nicht zweimal schreiben. Dafür gibt es extends.
extends und super
<p>Eine Basis, eine Ableitung, eine überschriebene Methode.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} class Kurs {
constructor(titel, stunden) {
this.titel = titel;
this.stunden = stunden;
}
preis() {
return "kostenlos";
}
beschreibung() {
return `${this.titel}, ${this.stunden} h, ${this.preis()}`;
}
}
class BezahlterKurs extends Kurs {
constructor(titel, stunden, euro) {
// super ruft den constructor der Basis auf.
super(titel, stunden);
this.euro = euro;
}
// Überschreibt preis, beschreibung bleibt geerbt.
preis() {
return `${this.euro} Euro`;
}
}
const frei = new Kurs("JavaScript", 47);
const bezahlt = new BezahlterKurs("Web Security", 18, 49);
console.log(frei.beschreibung());
console.log(bezahlt.beschreibung());
console.log("Ist ein BezahlterKurs auch ein Kurs?", bezahlt instanceof Kurs); class BezahlterKurs extends Kurs sagt: Diese Klasse kann alles, was Kurs kann, und dazu etwas Eigenes.
super(...) im constructor ruft den constructor der Basisklasse auf. Was der dort erledigt, musst du nicht wiederholen.
super.methode() ruft die Fassung der Basisklasse auf, auch wenn du sie überschrieben hast. Das brauchst du, wenn du etwas ergänzen statt ersetzen willst.
Achte im ersten Beispiel darauf, was beschreibung() tut. Sie steht nur in der Basisklasse und ruft this.preis() auf. Bei einem bezahlten Kurs landet dieser Aufruf trotzdem in der überschriebenen Fassung. Die Methode weiß nicht, in welcher Klasse sie gerade arbeitet, und genau das ist der Sinn der Sache.
super() steht vor jedem this
<p>Zweimal derselbe constructor, einmal in der falschen Reihenfolge.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} class Basis {
constructor(name) {
this.name = name;
}
}
class Richtig extends Basis {
constructor(name) {
super(name);
this.gross = name.toUpperCase();
}
}
class Falsch extends Basis {
constructor(name) {
// Vor super gibt es noch kein this.
this.gross = name.toUpperCase();
super(name);
}
}
console.log("Richtig:", new Richtig("mario").gross);
try {
console.log("Falsch:", new Falsch("mario").gross);
} catch (fehler) {
console.error(fehler.name + ":", fehler.message);
} Eine harte Regel, und sie kommt nicht mit einer freundlichen Warnung, sondern mit einem ReferenceError.
Der Grund ist der, den du aus 18.1 kennst: new legt das Objekt an, und dabei ist der constructor der Basisklasse beteiligt. Vor super() gibt es das Objekt noch gar nicht, also gibt es auch kein this, auf das man etwas schreiben könnte.
Merksatz: Erst super(), dann alles andere.
Und jetzt die ehrliche Warnung
Vererbung sieht auf den ersten Blick nach Ordnung aus, und in der dritten Ebene ist sie meistens keine mehr.
Das Problem ist nicht die Technik, sondern die Behauptung dahinter. extends sagt: Dieses Ding ist eines von jener Sorte, in jeder Hinsicht, für immer. Das stimmt beim Schreiben oft und ein Jahr später oft nicht mehr, und dann steht in der Basisklasse eine Methode, die für drei von fünf Ableitungen keinen Sinn ergibt.
Die Alternative heißt Zusammensetzen: Statt von einer Klasse zu erben, bekommt ein Objekt das, was es braucht, als Bestandteil mit. Das ist unspektakulär und hält länger.
Praktische Faustregel: Eine Ebene Vererbung ist meistens in Ordnung. Ab der zweiten frag dich, ob du wirklich eine Ist-Beziehung beschreibst oder nur Code sparen wolltest.
Der Fall, in dem sie wirklich nützt
<p>Der eine Fall, in dem Vererbung im Alltag wirklich nützt.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} class KursFehler extends Error {
constructor(meldung, kennung) {
super(meldung);
this.name = "KursFehler";
this.kennung = kennung;
}
}
function holeKurs(kennung) {
if (kennung !== "javascript") {
throw new KursFehler(`Kurs nicht gefunden: ${kennung}`, kennung);
}
return { titel: "JavaScript" };
}
function versuchen(kennung) {
try {
console.log("Gefunden:", holeKurs(kennung).titel);
} catch (fehler) {
// Jetzt lässt sich der eigene Fehler vom Rest trennen.
if (fehler instanceof KursFehler) {
console.warn("Eigener Fehler:", fehler.message, "| Kennung:", fehler.kennung);
} else {
console.error("Fremder Fehler:", fehler.message);
}
}
}
versuchen("javascript");
versuchen("git");
console.log("name:", new KursFehler("x", "y").name);
console.log("Ist es ein Error?", new KursFehler("x", "y") instanceof Error); Es gibt eine Anwendung, die dir im Alltag ständig begegnet, und dort ist Vererbung genau richtig: eigene Fehlerklassen als Fortsetzung von Lektion 14.3.
class KursFehler extends Error gibt dir einen Fehler, der alles kann, was ein Error kann, und dazu deine eigenen Felder trägt. Im catch lässt er sich dann mit instanceof von allem anderen unterscheiden, und du behandelst deinen eigenen Fehler anders als einen, mit dem du nicht gerechnet hast.
Zwei Kleinigkeiten dabei:
super(meldung) gibt die Meldung an Error weiter. Sie landet in fehler.message, und ohne diesen Aufruf bleibt sie leer.
this.name = "KursFehler" setzt den Namen, der in der Console vor der Meldung steht. Ohne diese Zeile steht dort weiterhin Error, und die Fehlermeldung verschweigt genau die Information, für die du die Klasse gebaut hast.
Ausprobieren kannst du das im dritten Beispiel: console.error(new KursFehler("Kurs nicht gefunden: git", "git")) in der Eingabezeile der Console erzeugt dort die Zeile KursFehler: Kurs nicht gefunden: git. Genau so sieht ein Fehler aus, den niemand gefangen hat, und der Name ist das Erste, was jemand liest.
Der Name und die Meldung sind dabei zwei verschiedene Dinge. Ein catch prüft die Art des Fehlers mit instanceof oder über fehler.name, nie über den Text der Meldung: Texte ändern sich, und in einer anderen Sprache stimmen sie ohnehin nicht mehr.
Zum Mitnehmen
Vererbung ist im Alltag selten die richtige Antwort. Der eine Fall, in dem sie es wirklich ist: eigene Fehlerklassen.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.