Abschnitt 18 · Lektion 3
Private Felder, Getter und statische Mitglieder
Bis hier war alles an einem Objekt von außen erreichbar und veränderbar. Diese Lektion legt fest, was von außen sichtbar ist und was nicht.
Das Doppelkreuz
<p>Zwei Felder, die beide privat sein sollen. Nur eines ist es.</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 Konto {
#stand = 100;
_stand = 100;
einzahlen(betrag) {
this.#stand = this.#stand + betrag;
this._stand = this._stand + betrag;
}
bericht() {
return `privat: ${this.#stand}, mit Unterstrich: ${this._stand}`;
}
}
const konto = new Konto();
konto.einzahlen(50);
console.log(konto.bericht());
// Der Unterstrich hält niemanden auf.
konto._stand = 999999;
console.log("Nach dem Zugriff von außen:", konto.bericht());
// Das Doppelkreuz-Feld ist von außen unsichtbar.
console.log("Sichtbare Felder:", Object.keys(konto));
console.log("Als JSON:", JSON.stringify(konto));
console.log("Gibt es eine Eigenschaft stand?", "stand" in konto); Ein Feldname, der mit # beginnt, ist privat. Nicht der Übereinkunft nach, sondern wirklich: Von außerhalb der Klasse gibt es keinen Weg heran.
Es taucht in Object.keys nicht auf. JSON.stringify schreibt es nicht mit. Und der Zugriff von außen ist kein stiller undefined, sondern ein Fehler beim Einlesen der Datei, noch bevor eine Zeile läuft. Der Browser meldet ihn als SyntaxError mit dem Hinweis, dass dieses private Feld hier nicht deklariert ist.
Jahrelang gab es dafür nur den Unterstrich: this._stand. Der war ein Zettel mit der Aufschrift „bitte nicht anfassen” und hat genau so viel bewirkt wie ein solcher Zettel. Im ersten Beispiel siehst du beides nebeneinander, und du siehst, wie leicht sich der Unterstrich überschreiben lässt.
Den Unterschied kannst du in vier Zeilen selbst nachstellen. Öffne das erste Beispiel und frag in der Eingabezeile der Console nacheinander:
konto antwortet mit Konto {_stand: 999999}. Ein Feld, obwohl die Klasse zwei hat.
konto._stand antwortet mit 999999. Der Zettel hält niemanden auf.
konto.#stand antwortet mit Uncaught SyntaxError: Private field '#stand' must be declared in an enclosing class. Das ist kein undefined und kein Laufzeitfehler, sondern eine Zeile, die der Browser gar nicht erst einliest.
konto.bericht() antwortet mit privat: 150, mit Unterstrich: 999999. Von innen kommt die Klasse an beides heran, und man sieht auf einen Blick, welches der beiden Felder jemand von außen verstellt hat.
Zwei Kleinigkeiten dazu: Das # gehört zum Namen, deshalb schreibst du auch beim Lesen this.#stand. Und private Felder müssen im Klassenkopf deklariert sein, es reicht nicht, sie irgendwo im constructor zum ersten Mal zuzuweisen.
Getter und Setter
<p>Sieht aus wie ein Feld, ist eine 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 Temperatur {
#celsius = 0;
get celsius() {
return this.#celsius;
}
set celsius(wert) {
// Ein Setter ist die Stelle, an der geprüft wird.
if (typeof wert !== "number" || Number.isNaN(wert)) {
throw new TypeError("Temperatur braucht eine Zahl");
}
this.#celsius = wert;
}
// Ein berechneter Wert. Kein Feld, nirgends gespeichert.
get fahrenheit() {
return this.#celsius * 1.8 + 32;
}
}
const t = new Temperatur();
// Keine Klammern. Es liest sich wie eine Eigenschaft.
t.celsius = 20;
console.log("Celsius:", t.celsius);
console.log("Fahrenheit:", t.fahrenheit);
try {
t.celsius = "warm";
} catch (fehler) {
console.warn("Abgewiesen:", fehler.message);
}
console.log("Unverändert:", t.celsius); Ein get vor einer Methode macht daraus etwas, das sich beim Benutzen wie ein Feld liest: t.celsius statt t.celsius(). Ein set macht dasselbe für das Schreiben.
Dafür gibt es zwei gute Gründe:
Ein berechneter Wert. fahrenheit ist nirgends gespeichert, es wird bei jedem Zugriff aus #celsius gerechnet. Damit kann es nie veralten, anders als ein Feld, das man beim Ändern mit aktualisieren müsste.
Eine Prüfung beim Schreiben. Der Setter ist die einzige Tür zum privaten Feld, also die Stelle, an der du kontrollierst, was hineindarf.
Und eine Warnung: Ein get, das teuer rechnet, ist eine Falle. Es sieht am Aufrufort aus wie ein Feldzugriff, und niemand rechnet damit, dass eine Zeile wie if (x.gesamtpreis > 100) eine Schleife über zehntausend Einträge auslöst. Was länger dauert als eine einfache Rechnung, gehört in eine Methode mit Klammern.
static
<p>Gehört zur Klasse, nicht zum einzelnen Objekt.</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 {
static anzahl = 0;
constructor(titel, stunden) {
this.titel = titel;
this.stunden = stunden;
Kurs.anzahl = Kurs.anzahl + 1;
}
// Eine Fabrikmethode: erzeugt ein Objekt aus etwas
// anderem als den fertigen Einzelteilen.
static ausText(zeile) {
const [titel, stunden] = zeile.split(";");
return new Kurs(titel, Number(stunden));
}
}
const a = new Kurs("HTML", 13);
const b = Kurs.ausText("JavaScript;47");
console.log(b.titel, b.stunden, typeof b.stunden);
console.log("Erzeugte Kurse:", Kurs.anzahl);
// static hängt an der Klasse, nicht am Objekt.
console.log("Am Objekt:", a.anzahl);
console.log("An der Klasse:", Kurs.anzahl); static macht aus einem Feld oder einer Methode etwas, das zur Klasse gehört und nicht zum einzelnen Objekt.
Aufgerufen wird es über den Klassennamen, Kurs.anzahl und nicht meinKurs.anzahl. Am Objekt ist es schlicht nicht da, und das siehst du im dritten Beispiel.
Die häufigste Anwendung ist eine Fabrikmethode: eine Funktion, die ein Objekt aus etwas anderem baut als den fertigen Einzelteilen. Kurs.ausText("JavaScript;47") zerlegt eine Zeile und gibt einen fertigen Kurs zurück.
Warum nicht einfach eine gewöhnliche Funktion daneben? Weil sie so bei der Klasse steht, zu der sie gehört. Wer Kurs liest, findet sie, ohne die Datei zu durchsuchen. Das ist der ganze Vorteil, und er reicht.
Was du davon wirklich brauchst
Private Felder und Getter sind kein Selbstzweck. Sie zahlen sich an einer Stelle aus, und die kommt in der nächsten Lektion: Sobald ein Objekt einen Zustand führt, den nur es selbst richtig ändern kann, ist alles andere eine Einladung zum Durcheinander.
Ein Zähler, dessen Stand jeder von außen setzen darf, ist kein Zähler, sondern eine Zahl mit Zusatzfunktionen.
Zum Mitnehmen
Das Doppelkreuz macht ein Feld wirklich privat. Der Unterstrich war immer nur ein Zettel mit der Aufschrift bitte nicht anfassen.
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.