Abschnitt 7 · Lektion 4
Verschachtelte Daten
Echte Daten sind selten flach. Ein Teilnehmer hat eine Adresse, die Adresse hat einen Ort, und die Kursliste enthält wieder Objekte. Das Lesen ist dabei nicht das Problem, sondern der Fall, in dem eine Ebene fehlt.
Die Kette
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const teilnehmerin = {
name: "Ida",
adresse: {
ort: "Berlin",
plz: "10115",
},
kurse: ["HTML", "CSS", "JavaScript"],
fortschritt: [
{ kurs: "HTML", prozent: 100 },
{ kurs: "CSS", prozent: 60 },
],
};
// Ein Objekt im Objekt: zwei Punkte.
console.log(teilnehmerin.adresse.ort);
// Eine Liste im Objekt: Punkt, dann eckige Klammern.
console.log(teilnehmerin.kurse[0]);
// Objekte in einer Liste in einem Objekt: alles zusammen.
console.log(teilnehmerin.fortschritt[1].prozent);
// Die Kette wird von links nach rechts gelesen. Jeder Schritt
// liefert etwas, auf dem der nächste arbeitet.
console.log(teilnehmerin.fortschritt.length); Ein Wert in einem Objekt darf selbst ein Objekt sein, oder eine Liste, oder eine Liste aus Objekten. Zum Lesen hängst du die Zugriffe aus Lektion 7.1 einfach aneinander: Punkt für einen Schlüssel, eckige Klammern für eine Position.
Gelesen wird von links nach rechts, und jeder Schritt arbeitet auf dem Ergebnis des vorherigen. teilnehmerin.fortschritt[1].prozent holt erst das Objekt, dann daraus die Liste, dann daraus den zweiten Eintrag, dann daraus die Zahl.
Das klingt banal, ist aber der Schlüssel zur Fehlersuche im Rest der Lektion. Wenn so eine Kette abbricht, hilft es, sie in der Console Stück für Stück nachzubauen und zu schauen, an welcher Stelle zum ersten Mal undefined steht.
Der Abbruch
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const ohneAdresse = { name: "Ben" };
// Die erste Ebene ist harmlos: es gibt keine adresse,
// also kommt undefined heraus. Kein Fehler.
console.log("adresse:", ohneAdresse.adresse);
// Die zweite Ebene bricht ab. Man liest hier eine Eigenschaft
// von undefined, und das geht nicht.
// Diese Zeile steht deshalb ganz unten: nach ihr läuft nichts mehr.
console.log("ort:", ohneAdresse.adresse.ort);
console.log("Diese Zeile wird nie erreicht"); Der Zugriff auf eine unbekannte Eigenschaft liefert undefined, das steht in Lektion 7.1 und gilt weiterhin. Nur ist undefined selbst kein Objekt, und deshalb bricht der nächste Schritt ab.
Die Meldung dazu ist eine der häufigsten in JavaScript überhaupt: TypeError: Cannot read properties of undefined. Sie nennt sogar, welche Eigenschaft gesucht wurde, meistens mit dem Zusatz (reading 'ort'). Firefox schreibt dieselbe Sache als can't access property "ort", mit.adresse is undefined und sagt damit ausdrücklich, welcher Teil der Kette gefehlt hat: das Vorletzte.
Und genau da steckt die Falle, die Anfänger jedes Mal erwischt. Die Meldung nennt ort, aber ort ist nicht das Problem. Das Problem ist adresse, denn die fehlt. Die Meldung sagt dir, welchen Schritt du gehen wolltest, und du brauchst den davor.
Wie jeder Fehler beendet auch dieser das Skript an dieser Stelle. Deshalb steht die Zeile im Beispiel ganz unten, und deshalb ist der zweite Effekt so verwirrend: es geht nicht nur eine Ausgabe verloren, sondern alles, was danach kam.
Sauber abbrechen mit ?.
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const mit = { name: "Ida", adresse: { ort: "Berlin" } };
const ohne = { name: "Ben" };
// Mit ?. bricht die Kette sauber ab, statt einen Fehler zu werfen.
console.log("Mit Adresse:", mit.adresse?.ort);
console.log("Ohne Adresse:", ohne.adresse?.ort);
// Auch vor eckigen Klammern und vor einem Aufruf, dort mit Punkt.
const daten = { liste: ["a", "b"] };
const leer = {};
console.log("Liste:", daten.liste?.[0]);
console.log("Keine Liste:", leer.liste?.[0]);
// Und die Zeile, die es ohne ?. gar nicht bis hierhin geschafft
// hätte. Sie läuft jetzt einfach durch.
console.log("Fertig");
// Eine Ebene zu früh gesetzt hilft es dagegen nicht: ohne gibt
// es, adresse nicht, und deshalb bricht diese Zeile ab.
// Sie steht darum ganz unten.
console.log("Zu früh gesetzt:", ohne?.adresse.ort); Die optionale Verkettung ?. prüft, was links davon steht. Ist das null oder undefined, hört die Kette an dieser Stelle auf und der ganze Ausdruck ergibt undefined. Ist es etwas anderes, geht es normal weiter.
Sie steht vor dem Schritt, der schiefgehen kann: ohne.adresse?.ort heißt „wenn es eine Adresse gibt, hol den Ort”. Vor eckigen Klammern und vor einem Aufruf schreibt man sie mit Punkt: liste?.[0] und funktion?.().
Die letzte Zeile des Beispiels zeigt, was passiert, wenn sie eine Ebene zu weit vorn steht. ohne?.adresse.ort fragt nach ohne, und das gibt es. Gefragt werden müsste nach adresse, und deshalb bricht die Zeile trotzdem ab. Das Fragezeichen gehört also nicht an den Anfang der Kette, sondern hinter das, was fehlen darf.
Schreib sie nicht überall hin. Es ist verlockend, jede Kette mit ?. zu pflastern und nie wieder einen TypeError zu sehen. Der Preis dafür ist hoch: ?. sagt dem Leser „hier darf etwas fehlen”, und wenn das an jeder Stelle steht, sagt es gar nichts mehr. Schlimmer noch, ein echter Fehler in den Daten fällt dann nicht mehr auf, er wird nur stiller.
Die Regel lautet: ?. dort, wo das Fehlen vorgesehen ist. Überall sonst soll es krachen, denn dann stimmt etwas nicht.
Und dann ein Standardwert
<h1>Ausgabe</h1>
<p>Was dieses Beispiel tut, steht in der Console.</p> const ohne = { name: "Ben" };
// ?. liefert undefined, ?? macht daraus einen brauchbaren Wert.
console.log(ohne.adresse?.ort ?? "unbekannt");
// Der Unterschied zum Oder aus Lektion 3.4: ?? greift nur bei
// null und undefined, || bei allem Falschen.
const einstellungen = { anzahl: 0, titel: "" };
console.log("Mit ??:", einstellungen.anzahl ?? 10);
console.log("Mit ||:", einstellungen.anzahl || 10);
console.log("Mit ??:", einstellungen.titel ?? "Ohne Titel");
console.log("Mit ||:", einstellungen.titel || "Ohne Titel");
// Deshalb ist ?? fast immer das, was du meinst, sobald 0 oder
// der leere Text gültige Werte sind. ?. liefert im Fehlfall undefined, und damit will man selten weiterarbeiten. Der Partner dafür ist ??: er nimmt den linken Wert, außer der ist null oder undefined, dann den rechten.
ohne.adresse?.ort ?? "unbekannt" ist deshalb das Paar, das du im Rest des Kurses ständig sehen wirst. Erst sauber abbrechen, dann etwas Brauchbares einsetzen.
Der Unterschied zum || aus Lektion 3.4 ist der Punkt dieses Absatzes. || greift bei allem, was falsch genug ist, und dazu gehören 0 und der leere Text. Eine Anzahl von 0 würde damit durch den Standardwert ersetzt, obwohl 0 eine gültige Antwort ist. ?? greift nur bei den beiden Sorten von „nichts” aus Lektion 2.6 und lässt 0 und "" in Ruhe.
Sobald 0 oder der leere Text gültige Werte sein können, ist ?? also das, was du wirklich meinst. Das ist häufiger, als man denkt.
Zum Mitnehmen
Cannot read properties of undefined heißt nie, dass das Letzte in der Kette fehlt. Es heißt, dass das Vorletzte fehlt.
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 4 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.