mitmario.dev

Synthese: gemerkte Einstellungen

JavaScript Im Browser 4 Min Lesezeit 3 BeispieleLektion 4 von 5

Alles aus diesem Abschnitt an einem Stück, und dazu ein Muster, das du danach immer wieder benutzt.

Ein Objekt, zwei Funktionen

Ein Objekt, zwei Funktionen
const SCHLUESSEL = "wiedergabe";
const STANDARD = { lautstaerke: 5, wiederholen: false };

function laden() {
  const roh = localStorage.getItem(SCHLUESSEL);
  if (roh === null) {
    return { ...STANDARD };
  }
  try {
    return { ...STANDARD, ...JSON.parse(roh) };
  } catch (fehler) {
    return { ...STANDARD };
  }
}

function speichern() {
  localStorage.setItem(SCHLUESSEL, JSON.stringify(zustand));
}

// Der Zustand ist ein einziges Objekt. Der Rest des Codes
// arbeitet nur damit und sieht den Speicher nie.
let zustand = laden();

const anzeige = document.getElementById("anzeige");

function anzeigen() {
  anzeige.textContent = `Lautstärke: ${zustand.lautstaerke}`;
  console.log("Zustand:", JSON.stringify(zustand));
}

document.getElementById("lauter").addEventListener("click", () => {
  zustand.lautstaerke = Math.min(10, zustand.lautstaerke + 1);
  anzeigen();
  speichern();
});

document.getElementById("leiser").addEventListener("click", () => {
  zustand.lautstaerke = Math.max(0, zustand.lautstaerke - 1);
  anzeigen();
  speichern();
});

anzeigen();

Der Aufbau ist immer derselbe:

Ein einziges Objekt hält den gesamten Zustand.

Eine Funktion liest ihn beim Start aus dem Speicher.

Eine Funktion schreibt ihn weg.

Und der ganze Rest deines Codes arbeitet nur mit dem Objekt und fasst den Speicher nie an.

Der letzte Punkt ist der wichtige. Sobald ein localStorage.setItem mitten in einem Klick-Handler steht, ist die Umwandlung an dieser einen Stelle geregelt, und die nächste Stelle regelt sie wieder, ein bisschen anders. Mit zwei Funktionen gibt es genau einen Ort, an dem entschieden wird, wie gespeichert wird.

Und einen Ort, an dem der Ärger abgefangen wird. Klick im ersten Beispiel zweimal auf Lauter, dann steht in der Eingabezeile der Console hinter localStorage.getItem("wiedergabe") der Text {"lautstaerke":7,"wiederholen":false}. Mach ihn mit localStorage.setItem("wiedergabe", "{kaputt") absichtlich kaputt und klick danach auf Seite neu laden. Die Seite kommt bei Lautstärke: 5 wieder hoch, ohne Fehler, ohne leere Anzeige. Das catch in laden hat den Unsinn geschluckt und den Standardzustand zurückgegeben. Ein laden, das direkt JSON.parse zurückgibt, hätte an dieser Stelle die ganze Anwendung angehalten, und zwar bevor der erste Knopf verdrahtet ist.

Der Standardzustand liegt darunter

Der Standardzustand liegt darunter
const STANDARD = { thema: "hell", groesse: 16, kompakt: false };

// So sah der gespeicherte Zustand vor drei Versionen aus.
// Das Feld kompakt gab es damals noch nicht.
localStorage.setItem("alt", JSON.stringify({ thema: "dunkel", groesse: 20 }));

const gelesen = JSON.parse(localStorage.getItem("alt"));

console.log("Nur gelesen:", JSON.stringify(gelesen));
console.log("kompakt fehlt:", gelesen.kompakt);

// Der Standardzustand darunter, das Gelesene darüber.
const zustand = { ...STANDARD, ...gelesen };

console.log("Mit Standard:", JSON.stringify(zustand));
console.log("kompakt ist jetzt da:", zustand.kompakt);

// Andersherum wäre es falsch: dann gewinnt der Standard.
console.log("Verkehrt herum:", JSON.stringify({ ...gelesen, ...STANDARD }));

Beim Lesen reicht es nicht, das Gespeicherte zu nehmen. Der gespeicherte Stand ist womöglich alt.

Deine Anwendung hat vor drei Versionen eine Einstellung dazubekommen. Wer damals gespeichert hat, hat sie nicht mit abgelegt, und beim Lesen fehlt sie. Ab da rechnet dein Code mit undefined, und zwar nur bei den Nutzern, die schon lange dabei sind, also bei denen, die es am wenigsten verdient haben.

Der Ausweg ist eine Zeile, und es ist der Spread aus Lektion 7.6.

Erst der vollständige Standardzustand, dann das Gelesene darüber. Was gelesen wurde, gewinnt. Was fehlt, kommt aus dem Standard.

Die Reihenfolge ist alles. Andersherum gewinnt der Standard, und die gespeicherten Einstellungen des Nutzers verschwinden bei jedem Start. Im zweiten Beispiel stehen beide Fassungen untereinander.

Nicht bei jedem Tastendruck

Nicht bei jedem Tastendruck
const zaehler = document.getElementById("zaehler");
let sofortZahl = 0;
let spaeterZahl = 0;
let timer = null;

function zeigen() {
  zaehler.textContent = `Schreibvorgänge: sofort ${sofortZahl}, verzögert ${spaeterZahl}`;
}

document.getElementById("sofort").addEventListener("input", (ereignis) => {
  localStorage.setItem("sofort", ereignis.target.value);
  sofortZahl = sofortZahl + 1;
  zeigen();
});

document.getElementById("spaeter").addEventListener("input", (ereignis) => {
  // Jeder neue Tastendruck wirft den vorigen Termin weg.
  clearTimeout(timer);
  timer = setTimeout(() => {
    localStorage.setItem("spaeter", ereignis.target.value);
    spaeterZahl = spaeterZahl + 1;
    zeigen();
  }, 400);
});

zeigen();

Bei einem Knopf ist es egal: Ein Klick, ein Speichervorgang. Bei einem Textfeld nicht. Ein input-Ereignis feuert bei jedem Zeichen, und wer dort direkt speichert, schreibt bei einem Satz sechzig Mal in den Speicher.

Die Lösung heißt verzögertes Speichern, und du kennst beide Teile davon aus Lektion 15.1:

Beim Aufruf wird ein Termin auf ein paar hundert Millisekunden gesetzt. Kommt vorher ein weiterer Aufruf, wird der alte Termin mit clearTimeout weggeworfen und ein neuer gesetzt. Gespeichert wird also erst, wenn eine Weile nichts mehr passiert ist.

Im dritten Beispiel siehst du beide Zähler nebeneinander. Tipp ein Wort in beide Felder: Links steht die Zahl der Buchstaben, rechts eine Eins.

Warum rechts nur eine Eins steht, sagen die Zähler allerdings nicht. Dafür gibt es den Reiter Debug. Klick dort auf Aufzeichnen, geh zurück auf Browser und tipp in beide Felder dasselbe Wort mit fünf Buchstaben. Danach steht im Protokoll für das obere Feld fünfmal derselbe Dreisatz, setItem, sofortZahl + 1, zeigen(). Für das untere Feld stehen dort fünfmal nur die zwei Zeilen clearTimeout(timer) und timer = setTimeout(...), und die Nummer, die als Änderung daneben steht, ist jedes Mal eine andere: Jeder Tastendruck wirft den alten Termin weg und bucht einen neuen. Erst danach, ein einziges Mal, kommt die Zeile mit dem setItem.

Das ist das Entprellen in Schriftform, und auf der Seite selbst ist davon nichts zu sehen.

Der Preis ist ein kleines Zeitfenster, in dem der letzte Stand noch nicht im Speicher liegt. Für Einstellungen ist das unerheblich. Für etwas, das auf keinen Fall verlorengehen darf, speicherst du zusätzlich beim Verlassen des Feldes.

Was beim Zurücksetzen passieren muss

Zum Schluss der Teil, den fast jeder halb baut.

Zurücksetzen heißt drei Dinge, nicht eins:

Der Eintrag im Speicher wird gelöscht.

Das Zustandsobjekt bekommt wieder die Standardwerte.

Und die Anzeige wird neu gezeichnet.

Wer nur den Speicher leert, hat einen Zustand im Arbeitsspeicher, der nicht mehr zu dem passt, was gespeichert ist. Beim nächsten Klick wird er zurückgeschrieben, und das Zurücksetzen war umsonst. Wer nur das Objekt zurücksetzt, findet die alten Werte beim nächsten Laden wieder vor.

Zum Mitnehmen

Ein Objekt hält den Zustand, zwei Funktionen bringen ihn in den Speicher und zurück. Alles andere fasst den Speicher nie an.

Jetzt du

Basis Konto, kostenlos

Zu 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.