Abschnitt 18 · Lektion 1
Klassen
In Abschnitt 7 hast du Objekte gebaut, indem du sie hingeschrieben hast. Das funktioniert gut, solange es wenige sind.
Sobald du fünfzig gleichartige Dinge brauchst, willst du eine Anleitung statt fünfzig Abschriften. Das ist eine Klasse.
Der Aufbau
<p>Zwei Kurse aus derselben Anleitung.</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 ist das Objekt, das gerade entsteht.
this.titel = titel;
this.stunden = stunden;
}
// Eine Methode. Kein function davor, kein Komma dahinter.
info() {
return `${this.titel}, ${this.stunden} h`;
}
istLang() {
return this.stunden > 20;
}
}
const js = new Kurs("JavaScript", 47);
const html = new Kurs("HTML", 13);
console.log(js.info());
console.log(html.info());
console.log("JavaScript ist lang:", js.istLang());
console.log("HTML ist lang:", html.istLang());
// Zwei Objekte, kein gemeinsamer Zustand.
js.stunden = 50;
console.log("Nach der Änderung:", js.info(), "|", html.info()); Drei Teile, mehr sind es zunächst nicht:
class Kurs { ... } eröffnet die Anleitung. Klassennamen schreibt man groß, das ist reine Übereinkunft, aber eine, an die sich alle halten.
Der constructor läuft, wenn ein neues Objekt entsteht. Was er in this schreibt, hat das Objekt danach.
Alles Weitere im Block sind Methoden. Sie stehen ohne function davor und ohne Komma dahinter, und das ist der häufigste Tippfehler beim Umstieg vom Objektliteral.
Und new Kurs("JavaScript", 47) erzeugt daraus ein Objekt.
Was dabei herauskommt, kannst du dir ansehen: Tipp js in die Eingabezeile der Console, und darüber steht Kurs {titel: "JavaScript", stunden: 50}. Der Name vor der geschweiften Klammer ist der Klassenname, und daran erkennst du ein Objekt aus einer Klasse auf den ersten Blick. Ein hingeschriebenes Objekt hat ihn nicht.
this ist hier einfacher als in 7.3
In Lektion 7.3 war this eine Quelle von Ärger, weil es davon abhing, wie eine Funktion aufgerufen wurde.
In einer Klasse ist es geradlinig: this ist das Objekt, an dem die Methode gerade arbeitet. Im constructor ist es das Objekt, das gerade entsteht.
Die Falle aus 7.3 gilt trotzdem weiter. Wer eine Methode aus ihrem Objekt herauslöst und irgendwo als Rückruf übergibt, verliert this genauso wie damals. Der Ausweg ist derselbe: eine Pfeilfunktion drumherum.
Was new wirklich tut
<p>Drei Schritte, und was ohne new passiert.</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) {
console.log("Im constructor ist this:", typeof this);
this.titel = titel;
}
}
// new macht drei Dinge:
// 1. ein leeres Objekt anlegen
// 2. den constructor darauf laufen lassen
// 3. das Objekt zurückgeben
const js = new Kurs("JavaScript");
console.log("Ergebnis:", js.titel);
console.log("Ist es ein Kurs?", js instanceof Kurs);
console.log("Typ:", typeof js);
// Ohne new geht es nicht, und zwar laut.
try {
const kaputt = Kurs("JavaScript");
console.log("Kommt hier nie an:", kaputt);
} catch (fehler) {
console.error(fehler.name + ":", fehler.message);
} Drei Schritte:
Es legt ein leeres Objekt an.
Es lässt den constructor darauf laufen, mit this als diesem Objekt.
Es gibt das Objekt zurück, ganz ohne dass irgendwo ein return steht.
Vergisst du das new, bekommst du keinen halben Erfolg, sondern einen TypeError. Das ist Absicht: Bei den alten Konstruktorfunktionen aus der Zeit vor class lief der Aufruf ohne new einfach durch und schrieb seine Felder ins globale Objekt, und der Fehler fiel erst viel später auf.
Wann eine Klasse passt und wann nicht
<p>Eine Fabrikfunktion leistet dasselbe. Vergleich die beiden.</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;
} function macheKurs(titel, stunden) {
return {
titel: titel,
stunden: stunden,
info() {
return `${this.titel}, ${this.stunden} h`;
},
};
}
const js = macheKurs("JavaScript", 47);
const html = macheKurs("HTML", 13);
console.log(js.info());
console.log(html.info());
// Der Unterschied steckt darin, wo die Methode liegt.
console.log("Eigene Eigenschaften:", Object.keys(js));
// Bei einer Klasse liegt info am Prototyp und existiert
// genau einmal, egal wie viele Objekte es gibt.
class Kurs {
constructor(titel) {
this.titel = titel;
}
info() {
return this.titel;
}
}
console.log("Bei der Klasse:", Object.keys(new Kurs("CSS"))); Eine Klasse lohnt sich, wenn du viele gleichartige Dinge mit gleichem Verhalten brauchst. Kurse, Einträge, Spielfiguren, Komponenten.
Sie lohnt sich nicht für einen einzelnen Datensatz. Dafür reicht ein Objekt.
Und eine Klasse mit genau einer Instanz ist fast immer eine Sammlung von Funktionen, die sich als Klasse verkleidet hat. Da ist ein Modul mit ein paar Funktionen ehrlicher.
Das zweite Beispiel zeigt die Alternative: eine Funktion, die ein fertiges Objekt zurückgibt. Sie leistet dasselbe, und für kleine Fälle ist sie oft die bessere Wahl.
Prototypen, in einem Absatz
Der Unterschied zwischen den beiden Beispielen ist trotzdem nicht bloß Geschmack, und er zeigt sich an der Ausgabe von Object.keys.
Bei der Fabrikfunktion bekommt jedes erzeugte Objekt seine eigene Kopie der Methode. Bei der Klasse liegt die Methode an einer Stelle, dem sogenannten Prototyp, und alle Objekte greifen darauf zu. Bei fünf Objekten ist das egal, bei fünftausend nicht mehr.
Das lässt sich in der Eingabezeile der Console nachfragen, und zwar in beiden Beispielen an derselben Zeile. Beim Objekt aus der Fabrik antwortet Object.keys(js) mit ["titel", "stunden", "info"], drei Einträge, die Methode mittendrin. Beim Objekt aus der Klasse antwortet dieselbe Zeile mit ["titel", "stunden"]. Wo info stattdessen liegt, sagen zwei weitere Fragen: Object.getPrototypeOf(js) === Kurs.prototype antwortet true, und typeof Kurs.prototype.info antwortet function.
Eine Kleinigkeit noch, die im ersten Moment verwirrt: Object.keys(Kurs.prototype) antwortet trotzdem mit einer leeren Liste. Die Methode ist da, sie ist nur nicht zum Aufzählen gedacht. Wer eine Klasse durchzählen will, findet also weder am Objekt noch am Prototyp etwas, und beides ist Absicht.
Denselben Unterschied siehst du direkt am Objekt. js allein getippt antwortet beim Objekt aus der Klasse mit Kurs {titel: "JavaScript", stunden: 50}, beim Objekt aus der Fabrik mit {titel: "JavaScript", stunden: 47, info: ƒ info()}. Einmal der Klassenname und zwei Felder, einmal kein Name und die Methode mitten in den Daten.
Klassen sind in JavaScript ohnehin nur eine bequemere Schreibweise für diesen Prototyp-Mechanismus, der schon vorher da war. Mehr musst du darüber nicht wissen, um mit ihnen zu arbeiten. Wichtig ist es an genau einer Stelle: wenn du in fremdem Code auf Object.create oder prototype triffst, weißt du jetzt, dass dort dasselbe passiert, nur ausgeschrieben.
Zum Mitnehmen
Eine Klasse ist eine Bauanleitung für Objekte. Nichts daran ist neu, es ist Abschnitt 7 mit einer anderen Schreibweise.
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.