Abschnitt 17 · Lektion 2
Objekte speichern
Der Speicher kann nur Text. Alles, was mehr als ein Wort ist, muss also erst einer werden.
Was passiert, wenn man es einfach versucht
<p>Der klassische Fehler, und er wirft nicht.</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;
} const notiz = { id: 1, text: "Einkaufen", erledigt: false };
// Kein Fehler, keine Warnung. setItem wandelt still in Text um.
localStorage.setItem("notiz", notiz);
const zurueck = localStorage.getItem("notiz");
console.log("Gespeichert:", zurueck);
console.log("Typ:", typeof zurueck);
console.log("Der Text ist da:", zurueck.length, "Zeichen");
// Und der Inhalt ist weg.
console.log("id:", zurueck.id);
// Bei einem Array sieht es anders aus und ist genauso kaputt.
localStorage.setItem("liste", [1, 2, 3]);
console.log("Liste:", localStorage.getItem("liste"));
console.log("Erster Eintrag:", localStorage.getItem("liste")[0]); setItem beschwert sich nicht, wenn du ihm ein Objekt gibst. Es wandelt es still in Text um, und zwar auf dem Weg, den JavaScript für jedes Objekt nimmt, das zu Text werden soll: Heraus kommt die Zeichenfolge [object Object].
Das ist kein Fehler, sondern eine Umwandlung. Es wirft nichts, es warnt nichts, und im Speicher liegt danach etwas, das aussieht wie ein Wert und keinen enthält.
Bei einem Array ist es tückischer, denn dort sieht das Ergebnis fast richtig aus: [1, 2, 3] wird zu 1,2,3. Man erkennt die Zahlen wieder, und trotzdem ist es ein Text, aus dem niemand mehr ein Array macht, ohne selbst zu zerlegen.
Sieh es dir in der Eingabezeile der Console an, das ist hier der kürzeste Weg zur Wahrheit. localStorage.getItem("notiz") antwortet mit [object Object], localStorage.getItem("liste") mit 1,2,3, und typeof localStorage.getItem("liste") mit string. Drei Antworten, und die dritte erklärt die ersten beiden.
Der Umweg über JSON
<p>Derselbe Ablauf, diesmal mit dem Umweg über JSON.</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;
} const notizen = [
{ id: 1, text: "Einkaufen", erledigt: false },
{ id: 2, text: "Kurs weiterschreiben", erledigt: true },
];
// Beim Schreiben: stringify.
localStorage.setItem("notizen", JSON.stringify(notizen));
console.log("Im Speicher liegt Text:", typeof localStorage.getItem("notizen"));
console.log("Und zwar dieser:", localStorage.getItem("notizen"));
// Beim Lesen: parse.
const zurueck = JSON.parse(localStorage.getItem("notizen"));
console.log("Zurück ist es ein Array:", Array.isArray(zurueck));
console.log("Zweiter Text:", zurueck[1].text);
console.log("Offen:", zurueck.filter((n) => !n.erledigt).length); Damit ist Lektion 16.1 hier angekommen, und zwar als Handwerk:
Beim Schreiben JSON.stringify, beim Lesen JSON.parse.
Mehr ist es nicht. Was im Speicher liegt, ist danach ein JSON-Text, und was du zurückbekommst, ist wieder ein Array oder ein Objekt, mit dem filter, map und der Punktzugriff funktionieren.
Auch das kannst du nachsehen: Nach dem zweiten Beispiel antwortet localStorage.getItem("notizen") in der Eingabezeile mit dem vollständigen JSON-Text, und JSON.parse(localStorage.getItem("notizen"))[1].text gleich daneben mit Kurs weiterschreiben. Im Kasten liegt Text, im Programm liegt wieder ein Array.
Denk dabei an das, was JSON unterwegs verliert (16.1). Ein Date kommt als Text zurück und nicht als Datum. Wer Zeitpunkte speichert, legt sie deshalb gleich als Zahl ab, mit Date.now(), und macht beim Lesen wieder ein Datum daraus.
Warum das Lesen ins try/catch gehört
Beim Schreiben kann nichts schiefgehen. Beim Lesen schon, und der Grund ist unangenehmer, als er klingt.
Du weißt nicht, was in diesem Speicher liegt. Vielleicht hat es eine ältere Fassung deiner Anwendung geschrieben, mit einem anderen Aufbau. Vielleicht ist die Zeile abgeschnitten, weil der Speicher vollgelaufen ist. Vielleicht hat es jemand von Hand hineingeschrieben, denn ein Eintrag lässt sich mit einer einzigen Zeile Code überschreiben.
JSON.parse wirft dann, und ohne try/catch bricht deine Anwendung schon beim Start ab, und zwar bei genau dem Nutzer, dessen Speicher betroffen ist. Der kann nichts tun außer die Browserdaten zu löschen, und dass er das tun müsste, sagt ihm niemand.
Ein catch, das den Standardwert zurückgibt, kostet drei Zeilen und verhindert genau das.
Diesen Fall kannst du dir selbst herstellen. Öffne das dritte Beispiel und tipp in die Eingabezeile der Console localStorage.setItem("gibtesnicht", "{kaputt"), dann auf Seite neu laden. In der Console steht jetzt eine Zeile mehr, Unlesbarer Eintrag: gibtesnicht, und darunter unverändert Nie geschrieben: {"thema":"hell"}. Das catch hat zugeschlagen, der Aufrufer hat seinen Standardwert bekommen, und die Datei ist weitergelaufen.
Nimm zum Vergleich das try/catch heraus und lass nur return JSON.parse(roh) stehen. Dann bleibt von der Ausgabe die erste Zeile übrig, gefolgt von Uncaught SyntaxError: Expected property name or '}' in JSON at position 1. Die beiden Zeilen darunter kommen nie, denn die Datei ist an dieser Stelle zu Ende. So sieht es bei einem echten Nutzer aus, nur dass dort die ganze Anwendung nicht startet.
Zwei Funktionen, und dann nie wieder
<p>Zwei Funktionen, und der Rest des Codes sieht den Speicher nie wieder.</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 schreib(schluessel, wert) {
localStorage.setItem(schluessel, JSON.stringify(wert));
}
function lies(schluessel, standard) {
const roh = localStorage.getItem(schluessel);
if (roh === null) {
return standard;
}
try {
return JSON.parse(roh);
} catch (fehler) {
// Was hier liegt, hat vielleicht eine ältere Fassung
// der Anwendung geschrieben. Oder jemand von Hand.
console.warn("Unlesbarer Eintrag:", schluessel);
return standard;
}
}
schreib("einstellungen", { thema: "dunkel", groesse: 16 });
console.log("Gelesen:", JSON.stringify(lies("einstellungen", {})));
console.log("Nie geschrieben:", JSON.stringify(lies("gibtesnicht", { thema: "hell" })));
localStorage.setItem("kaputt", "{das ist kein JSON");
console.log("Kaputt:", JSON.stringify(lies("kaputt", []))); Der Punkt, an dem man aufhört, localStorage überall im Code zu verstreuen.
schreib(schluessel, wert) und lies(schluessel, standard): Zusammen sind das ein Dutzend Zeilen, und danach ruft der Rest deines Codes nur noch diese beiden auf.
Der Gewinn ist nicht, dass es kürzer wird. Der Gewinn ist, dass die drei Entscheidungen genau einmal getroffen werden:
Wie umgewandelt wird, steht an einer Stelle.
Was bei kaputtem Inhalt passiert, steht an einer Stelle.
Und wenn du später vom Speicher auf einen Server umziehst, änderst du zwei Funktionen statt vierzig Aufrufe.
Achte im dritten Beispiel auf den Parameter standard. Der Standardwert kommt vom Aufrufer, denn nur der weiß, ob ein fehlender Eintrag ein leeres Array bedeutet oder ein leeres Objekt. Eine Funktion, die selbst null zurückgibt, verschiebt diese Entscheidung nur an vierzig andere Stellen.
Zum Mitnehmen
Ein Objekt direkt in den Speicher zu schreiben ergibt die Zeichenfolge [object Object]. Kein Fehler, nur Datenverlust.
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.