Client-seitige Speicherung
Moderne Webbrowser unterstützen verschiedene Möglichkeiten, wie Websites Daten auf dem Computer des Benutzers speichern können – mit dessen Erlaubnis – und sie dann bei Bedarf abrufen können. Dadurch können Sie Daten für langfristige Speicherung beibehalten, Websites oder Dokumente für die Offline-Nutzung speichern, benutzerspezifische Einstellungen für Ihre Website speichern und mehr. Dieser Artikel erklärt die grundlegenden Funktionsweisen dieser Methoden.
| Voraussetzungen: | Vertrautheit mit HTML, CSS und JavaScript, insbesondere JavaScript-Objektgrundlagen und grundlegende API-Abdeckung wie DOM-Scripting und Netzwerkanfragen. |
|---|---|
| Lernziele: |
|
Client-seitige Speicherung?
An anderer Stelle im MDN-Lernbereich haben wir über den Unterschied zwischen statischen Seiten und dynamischen Seiten gesprochen. Die meisten modernen großen Websites sind dynamisch — sie speichern Daten auf dem Server mittels einer Art von Datenbank (serverseitige Speicherung) und führen serverseitigen Code aus, um die benötigten Daten abzurufen, sie in statische Seitentemplates einzufügen und das resultierende HTML dem Client zur Darstellung im Browser des Nutzers zu liefern.
Die client-seitige Speicherung basiert auf ähnlichen Prinzipien, hat aber unterschiedliche Anwendungen. Sie besteht aus JavaScript-APIs, die es ermöglichen, Daten auf dem Client (d.h. auf dem Computer des Nutzers) zu speichern und bei Bedarf abzurufen. Dies hat viele verschiedene Anwendungen, wie zum Beispiel:
- Personalisierung der Website-Präferenzen (z.B. Anzeige benutzerdefinierter Widgets, Farbschema oder Schriftgröße).
- Beibehaltung vorheriger Website-Aktivitäten (z.B. Speicherung des Inhalts eines Warenkorbs aus einer vorherigen Sitzung, Erinnerung daran, ob ein Nutzer bereits eingeloggt war).
- Lokale Speicherung von Daten und Assets, sodass eine Website schneller (und potenziell kostengünstiger) heruntergeladen werden kann oder ohne Netzwerkverbindung nutzbar ist.
- Lokale Speicherung von Dokumenten, die von Web-Anwendungen generiert wurden, zur Offline-Verwendung.
Oft werden client-seitige und serverseitige Speicherung gemeinsam verwendet. Beispielsweise könnten Sie eine Reihe von Musikdateien herunterladen (vielleicht verwendet von einem Webspiel oder einer Musikplayer-Anwendung), diese in einer client-seitigen Datenbank speichern und bei Bedarf abspielen. Der Benutzer müsste die Musikdateien nur einmal herunterladen — bei nachfolgenden Besuchen würden sie aus der Datenbank abgerufen.
Hinweis: Es gibt Beschränkungen hinsichtlich der Menge an Daten, die Sie mit den APIs zur client-seitigen Speicherung speichern können (möglicherweise sowohl für jede einzelne API als auch kumulativ); das genaue Limit variiert abhängig vom Browser und möglicherweise von den Benutzereinstellungen. Weitere Informationen finden Sie unter Speicherquoten und Verarbeitungskriterien in Browsern.
Traditionell: Cookies
Das Konzept der client-seitigen Speicherung gibt es schon seit langer Zeit. Seit den frühen Tagen des Webs verwenden Websites Cookies, um Informationen zur Personalisierung der Benutzererfahrung auf Websites zu speichern. Sie sind die früheste Form der client-seitigen Speicherung, die im Web allgemein verwendet wurde.
Heutzutage gibt es einfachere Mechanismen zur Speicherung von client-seitigen Daten, weshalb wir Ihnen in diesem Artikel nicht beibringen, wie man Cookies verwendet. Das bedeutet jedoch nicht, dass Cookies im modernen Web völlig nutzlos sind — sie werden immer noch häufig zur Speicherung von Daten im Zusammenhang mit Benutzerpersonalisierung und -zustand verwendet, z.B. Sitzungs-IDs und Zugriffstoken. Weitere Informationen zu Cookies finden Sie in unserem Artikel Verwendung von HTTP-Cookies.
Moderne Lösungen: Web Storage und IndexedDB
Die oben genannten „einfacheren“ Funktionen sind wie folgt:
- Die Web Storage API bietet einen Mechanismus zur Speicherung und zum Abrufen kleinerer Datenobjekte, die aus einem Namen und einem entsprechenden Wert bestehen. Dies ist nützlich, wenn Sie nur einige einfache Daten speichern müssen, wie den Namen des Benutzers, ob er eingeloggt ist, welche Farbe für den Hintergrund des Bildschirms verwendet werden soll usw.
- Die IndexedDB API bietet dem Browser ein vollständiges Datenbanksystem zur Speicherung komplexer Daten. Diese kann für alles verwendet werden, von vollständigen Sets von Kundendaten bis hin zu komplexen Datentypen wie Audio- oder Videodateien.
Sie erfahren im Folgenden mehr über diese APIs.
Die Cache API
Die Cache API ist dafür konzipiert, HTTP-Antworten auf spezifische Anfragen zu speichern, und ist sehr nützlich für Dinge wie das Speichern von Website-Assets offline, sodass die Website anschließend ohne Netzwerkverbindung genutzt werden kann. Cache wird normalerweise in Kombination mit der Service Worker API verwendet, obwohl es nicht erforderlich ist.
Die Verwendung von Cache und Service Workern ist ein fortgeschrittenes Thema, und wir werden es in diesem Artikel nicht ausführlich behandeln, obwohl wir ein Beispiel im Abschnitt Offline-Asset-Speicherung weiter unten zeigen werden.
Einfache Datenspeicherung — Web Storage
Die Web Storage API ist sehr einfach zu verwenden — Sie speichern einfache Name/Wert-Paare von Daten (beschränkt auf Zeichenfolgen, Zahlen usw.) und rufen diese bei Bedarf ab.
Grundsyntax
Wir zeigen Ihnen, wie das geht:
-
Gehen Sie zunächst zu unserer Web Storage-Vorlage auf GitHub (öffnen Sie diese in einem neuen Tab).
-
Öffnen Sie die JavaScript-Konsole der Entwicklerwerkzeuge Ihres Browsers.
-
Alle Ihre Web Storage-Daten sind in zwei objektähnlichen Strukturen innerhalb des Browsers enthalten:
sessionStorageundlocalStorage. Die erste speichert Daten, solange der Browser geöffnet ist (die Daten gehen verloren, wenn der Browser geschlossen wird), und die zweite speichert Daten auch nach dem Schließen und erneutes Öffnen des Browsers. Wir werden in diesem Artikel die zweite verwenden, da sie im Allgemeinen nützlicher ist.Die Methode
Storage.setItem()ermöglicht es Ihnen, einen Datenitem im Speicher zu speichern — sie akzeptiert zwei Parameter: den Namen des Items und seinen Wert. Versuchen Sie, dies in Ihre JavaScript-Konsole einzugeben (ändern Sie den Wert in Ihren eigenen Namen, wenn Sie möchten!):jslocalStorage.setItem("name", "Chris"); -
Die Methode
Storage.getItem()akzeptiert einen Parameter — den Namen eines Datenitems, das Sie abrufen möchten — und gibt den Wert des Items zurück. Geben Sie jetzt diese Zeilen in Ihre JavaScript-Konsole ein:jslet myName = localStorage.getItem("name"); myName;Beim Eingeben der zweiten Zeile sollten Sie sehen, dass die Variable
myNamenun den Wert desname-Datenitems enthält. -
Die Methode
Storage.removeItem()akzeptiert einen Parameter — den Namen eines Datenitems, das Sie entfernen möchten — und entfernt dieses Item aus dem Web Storage. Geben Sie die folgenden Zeilen in Ihre JavaScript-Konsole ein:jslocalStorage.removeItem("name"); myName = localStorage.getItem("name"); myName;Die dritte Zeile sollte nun
nullzurückgeben — dasname-Item existiert nicht mehr im Web Storage.
Die Daten bleiben erhalten!
Ein Hauptmerkmal der Web Storage ist, dass die Daten zwischen Seitenladevorgängen gespeichert bleiben (und sogar wenn der Browser heruntergefahren wird, im Fall von localStorage). Lassen Sie uns dies in Aktion sehen.
-
Öffnen Sie unsere Web Storage-Vorlage erneut, aber diesmal in einem anderen Browser als dem, in dem Sie dieses Tutorial geöffnet haben! Das macht es einfacher damit umzugehen.
-
Geben Sie diese Zeilen in die JavaScript-Konsole des Browsers ein:
jslocalStorage.setItem("name", "Chris"); let myName = localStorage.getItem("name"); myName;Sie sollten sehen, dass das
name-Item zurückgegeben wird. -
Schließen Sie nun den Browser und öffnen ihn wieder.
-
Geben Sie die folgenden Zeilen erneut ein:
jslet myName = localStorage.getItem("name"); myName;Sie sollten sehen, dass der Wert immer noch verfügbar ist, obwohl der Browser geschlossen und dann wieder geöffnet wurde.
Separate Speicherung für jede Domäne
Es gibt einen separaten Datenspeicher für jede Domäne (jede separate Webadresse, die im Browser geladen wird). Sie werden feststellen, dass, wenn Sie zwei Websites laden (sagen wir google.com und amazon.com) und versuchen, ein Item auf einer Website zu speichern, es für die andere Website nicht verfügbar ist.
Das ist sinnvoll — man kann sich die Sicherheitsprobleme vorstellen, die auftreten würden, wenn Websites die Daten anderer Websites sehen könnten!
Ein umfangreicheres Beispiel
Lassen Sie uns dieses neu erworbene Wissen anwenden, indem wir ein funktionierendes Beispiel schreiben, um Ihnen einen Eindruck zu geben, wie Web Storage verwendet werden kann. Unser Beispiel ermöglicht es Ihnen, einen Namen einzugeben, nach dem die Seite so aktualisiert wird, dass sie Ihnen eine persönliche Begrüßung gibt. Dieser Zustand wird auch über Seiten-/Browserneustarts hinweg beibehalten, da der Name in der Web Storage gespeichert wird.
Sie finden das Beispiel-HTML unter personal-greeting.html — dies enthält eine Website mit einem Header, Inhalt und Footer sowie einem Formular zur Eingabe Ihres Namens.

Lassen Sie uns das Beispiel aufbauen, damit Sie verstehen, wie es funktioniert.
-
Erstellen Sie zunächst eine lokale Kopie unserer personal-greeting.html Datei in einem neuen Verzeichnis auf Ihrem Computer.
-
Beachten Sie als nächstes, wie unser HTML eine JavaScript-Datei namens
index.jsreferenziert, mit einer Zeile wie<script src="index.js" defer></script>. Diese müssen wir erstellen und unseren JavaScript-Code hineinschreiben. Erstellen Sie eineindex.jsDatei im selben Verzeichnis wie Ihre HTML-Datei. -
Wir beginnen damit, Referenzen zu allen HTML-Elementen zu erstellen, die wir in diesem Beispiel manipulieren müssen — wir erstellen sie alle als Konstanten, da diese Referenzen im Lebenszyklus der App nicht geändert werden müssen. Fügen Sie die folgenden Zeilen zu Ihrer JavaScript-Datei hinzu:
js// create needed constants const rememberDiv = document.querySelector(".remember"); const forgetDiv = document.querySelector(".forget"); const form = document.querySelector("form"); const nameInput = document.querySelector("#entername"); const submitBtn = document.querySelector("#submitname"); const forgetBtn = document.querySelector("#forgetname"); const h1 = document.querySelector("h1"); const personalGreeting = document.querySelector(".personal-greeting"); -
Als nächstes müssen wir einen kleinen Event-Listener einfügen, um zu verhindern, dass das Formular tatsächlich gesendet wird, wenn der Submit-Button gedrückt wird, da dies nicht das gewünschte Verhalten ist. Fügen Sie diesen Codeausschnitt unterhalb Ihres vorherigen Codes hinzu:
js// Stop the form from submitting when a button is pressed form.addEventListener("submit", (e) => e.preventDefault()); -
Nun müssen wir einen Event-Listener hinzufügen, dessen Handler-Funktion ausgeführt wird, wenn der „Say hello“-Button geklickt wird. Die Kommentare erklären im Detail, was jeder Teil macht, aber im Wesentlichen nehmen wir hier den Namen, den der Benutzer in das Text-Eingabefeld eingegeben hat, und speichern ihn mithilfe von
setItem()in der Web Storage, dann führen wir eine Funktion namensnameDisplayCheck()aus, die die Aktualisierung des tatsächlichen Website-Texts handhabt. Fügen Sie dies am Ende Ihres Codes hinzu:js// run function when the 'Say hello' button is clicked submitBtn.addEventListener("click", () => { // store the entered name in web storage localStorage.setItem("name", nameInput.value); // run nameDisplayCheck() to sort out displaying the personalized greetings and updating the form display nameDisplayCheck(); }); -
An diesem Punkt benötigen wir auch einen Event-Handler, um eine Funktion auszuführen, wenn der „Vergessen“-Button geklickt wird — dieser wird nur angezeigt, nachdem der „Say hello“-Button geklickt wurde (die beiden Formularzustände wechseln hin und her). In dieser Funktion entfernen wir das
name-Item mithilfe vonremoveItem()aus der Web Storage und führen dann erneutnameDisplayCheck()aus, um die Anzeige zu aktualisieren. Fügen Sie dies am Ende hinzu:js// run function when the 'Forget' button is clicked forgetBtn.addEventListener("click", () => { // Remove the stored name from web storage localStorage.removeItem("name"); // run nameDisplayCheck() to sort out displaying the generic greeting again and updating the form display nameDisplayCheck(); }); -
Es ist nun an der Zeit, die Funktion
nameDisplayCheck()selbst zu definieren. Hier prüfen wir, ob dasname-Item mithilfe vonlocalStorage.getItem('name')in der Web Storage gespeichert wurde, als bedingter Test. Wenn der Name gespeichert wurde, wird dieser Aufruf zutrueausgewertet; wenn nicht, wird der Aufruf zufalseausgewertet. Wenn der Aufruf zutrueausgewertet wird, zeigen wir eine personalisierte Begrüßung an, zeigen den „Vergessen“-Teil des Formulars an und verbergen den „Say hello“-Teil des Formulars. Wenn der Aufruf zufalseausgewertet wird, zeigen wir eine allgemeine Begrüßung an und tun das Gegenteil. Fügen Sie den folgenden Code erneut am Ende hinzu:js// define the nameDisplayCheck() function function nameDisplayCheck() { // check whether the 'name' data item is stored in web Storage if (localStorage.getItem("name")) { // If it is, display personalized greeting const name = localStorage.getItem("name"); h1.textContent = `Welcome, ${name}`; personalGreeting.textContent = `Welcome to our website, ${name}! We hope you have fun while you are here.`; // hide the 'remember' part of the form and show the 'forget' part forgetDiv.style.display = "block"; rememberDiv.style.display = "none"; } else { // if not, display generic greeting h1.textContent = "Welcome to our website "; personalGreeting.textContent = "Welcome to our website. We hope you have fun while you are here."; // hide the 'forget' part of the form and show the 'remember' part forgetDiv.style.display = "none"; rememberDiv.style.display = "block"; } } -
Zu guter Letzt müssen wir die Funktion
nameDisplayCheck()beim Laden der Seite ausführen. Wenn wir dies nicht tun, wird die personalisierte Begrüßung bei Reloads der Seite nicht beibehalten. Fügen Sie das folgende an das Ende Ihres Codes hinzu:jsnameDisplayCheck();
Ihr Beispiel ist fertig — gut gemacht! Es bleibt nur noch übrig, Ihren Code zu speichern und Ihre HTML-Seite in einem Browser zu testen. Sie können unsere fertige Version hier live sehen.
Hinweis: Es gibt ein weiteres, etwas komplexeres Beispiel zum Erkunden unter Verwenden der Web Storage API.
Hinweis:
In der Zeile <script src="index.js" defer></script> des Quellcodes unserer fertigen Version gibt das defer-Attribut an, dass der Inhalt des <script>-Elements erst ausgeführt wird, wenn die Seite vollständig geladen wurde.
Komplexe Datenspeicherung — IndexedDB
Die IndexedDB API (manchmal abgekürzt als IDB) ist ein komplettes Datenbanksystem, das im Browser verfügbar ist und in dem Sie komplexe, zusammenhängende Daten speichern können, die nicht auf einfache Werte wie Zeichenfolgen oder Zahlen beschränkt sind. Sie können Videos, Bilder und nahezu alles andere in einer IndexedDB-Instanz speichern.
Die IndexedDB API ermöglicht es Ihnen, eine Datenbank zu erstellen und dann darin Objektspeicher zu erstellen. Objektspeicher sind wie Tabellen in einer relationalen Datenbank, und jeder Objektspeicher kann eine Vielzahl von Objekten enthalten. Um mehr über die IndexedDB API zu erfahren, siehe Verwendung von IndexedDB.
Dies hat jedoch seinen Preis: IndexedDB ist viel komplexer zu verwenden als die Web Storage API. In diesem Abschnitt werden wir nur an der Oberfläche dessen kratzen, was möglich ist, aber wir geben Ihnen genug, um loszulegen.
Durchlaufen eines Notizspeicher-Beispiels
Hier werden wir Ihnen ein Beispiel näherbringen, das es Ihnen ermöglicht, Notizen in Ihrem Browser zu speichern und sie jederzeit anzusehen und zu löschen, wobei Sie es selbst aufbauen und die grundlegendsten Teile von IDB erklären.
Die App sieht ungefähr so aus:

Jede Notiz hat einen Titel und einen Body-Text, die einzeln bearbeitbar sind. Der JavaScript-Code, den wir im Folgenden durchgehen, enthält detaillierte Kommentare, um Ihnen das Verständnis zu erleichtern.
Erste Schritte
- Erstellen Sie zunächst lokale Kopien unserer
index.html,style.cssundindex-start.jsDateien in einem neuen Verzeichnis auf Ihrem lokalen Computer. - Sehen Sie sich die Dateien an. Sie werden sehen, dass das HTML eine Website mit einem Header und Footer sowie einem Hauptinhaltbereich definiert, der einen Platz zum Anzeigen von Notizen und ein Formular zum Eingeben neuer Notizen in die Datenbank enthält. Das CSS bietet ein wenig Styling, um klarer zu machen, was vor sich geht. Die JavaScript-Datei enthält fünf deklarierte Konstanten, die Referenzen auf das
<ul>-Element enthalten, in dem die Notizen angezeigt werden, sowie auf die Titel- und Body-<input>-Elemente, das<form>selbst und den<button>. - Benennen Sie Ihre JavaScript-Datei in
index.jsum. Sie sind nun bereit, Code hinzuzufügen.
Erste Einrichtung der Datenbank
Schauen wir uns nun an, was wir zuerst tun müssen, um tatsächlich eine Datenbank einzurichten.
-
Fügen Sie unterhalb der Konstantendeklarationen die folgenden Zeilen hinzu:
js// Create an instance of a db object for us to store the open database in let db;Hier deklarieren wir eine Variable namens
db— diese wird später verwendet, um ein Objekt zu speichern, das unsere Datenbank repräsentiert. Wir werden dies an einigen Stellen verwenden, daher haben wir es hier global deklariert, um es einfacher zu machen. -
Als nächstes fügen Sie die folgenden Zeilen hinzu:
js// Open our database; it is created if it doesn't already exist // (see the upgradeneeded handler below) const openRequest = window.indexedDB.open("notes_db", 1);Diese Zeile erstellt eine Anfrage zum Öffnen der Version
1einer Datenbank namensnotes_db. Wenn diese noch nicht existiert, wird sie von nachfolgendem Code erstellt. Sie werden sehen, dass dieses Anforderungsmuster in IndexedDB sehr oft verwendet wird. Datenbankoperationen nehmen Zeit in Anspruch. Sie wollen nicht, dass der Browser hängt, während Sie auf die Ergebnisse warten, daher sind Datenbankoperationen asynchron, was bedeutet, dass sie nicht sofort geschehen, sondern zu einem bestimmten Zeitpunkt in der Zukunft, und Sie benachrichtigt werden, wenn sie abgeschlossen sind.Um dies in IndexedDB zu handhaben, erstellen Sie ein Anfrageobjekt (das alles genannt werden kann, was Sie möchten — wir haben es hier
openRequestgenannt, um klar zu machen, wofür es gedacht ist). Sie verwenden dann Ereignishandler, um Code auszuführen, wenn die Anfrage abgeschlossen, fehlgeschlagen usw. ist, was Sie unten in Aktion sehen werden.Hinweis: Die Versionsnummer ist wichtig. Wenn Sie Ihre Datenbank aktualisieren möchten (zum Beispiel durch Ändern der Tabellenstruktur), müssen Sie Ihren Code erneut mit einer erhöhten Versionsnummer ausführen, einer anderen Schema, das im
upgradeneeded-Handler angegeben ist (siehe unten), usw. Wir werden das Upgraden von Datenbanken in diesem Tutorial nicht behandeln. -
Fügen Sie nun die folgenden Ereignishandler direkt unter Ihrer vorherigen Hinzufügung hinzu:
js// error handler signifies that the database didn't open successfully openRequest.addEventListener("error", () => console.error("Database failed to open"), ); // success handler signifies that the database opened successfully openRequest.addEventListener("success", () => { console.log("Database opened successfully"); // Store the opened database object in the db variable. This is used a lot below db = openRequest.result; // Run the displayData() function to display the notes already in the IDB displayData(); });Der
errorEreignishandler wird ausgeführt, falls das System mitteilt, dass die Anfrage fehlgeschlagen ist. Dies ermöglicht Ihnen, auf dieses Problem zu reagieren. In unserem Beispiel drucken wir einfach eine Nachricht auf die JavaScript-Konsole.Der
successEreignishandler wird ausgeführt, wenn die Anfrage erfolgreich zurückkehrt und die Datenbank erfolgreich geöffnet wurde. Wenn dies der Fall ist, wird ein Objekt, das die geöffnete Datenbank repräsentiert, in deropenRequest.result-Eigenschaft verfügbar gemacht, sodass wir die Datenbank manipulieren können. Wir speichern dies in derdb-Variablen, die wir zuvor erstellt haben, zur späteren Verwendung. Wir führen auch eine Funktion namensdisplayData()aus, die die Daten in der Datenbank innerhalb der<ul>anzeigt. Wir führen sie jetzt aus, damit die Notizen, die bereits in der Datenbank sind, angezeigt werden, sobald die Seite geladen wird. Sie werdendisplayData()weiter unten definiert sehen. -
Schließlich in diesem Abschnitt fügen wir den wahrscheinlich wichtigsten Ereignishandler zur Einrichtung der Datenbank hinzu:
upgradeneeded. Dieser Handler wird ausgeführt, wenn die Datenbank noch nicht eingerichtet wurde oder wenn die Datenbank mit einer höheren Versionsnummer als der bestehenden gespeicherten Datenbank geöffnet wird (beim Ausführen eines Upgrades). Fügen Sie den folgenden Code unter Ihrem vorherigen Handler hinzu:js// Set up the database tables if this has not already been done openRequest.addEventListener("upgradeneeded", (e) => { // Grab a reference to the opened database db = e.target.result; // Create an objectStore in our database to store notes and an auto-incrementing key // An objectStore is similar to a 'table' in a relational database const objectStore = db.createObjectStore("notes_os", { keyPath: "id", autoIncrement: true, }); // Define what data items the objectStore will contain objectStore.createIndex("title", "title", { unique: false }); objectStore.createIndex("body", "body", { unique: false }); console.log("Database setup complete"); });Hier definieren wir das Schema (Struktur) unserer Datenbank; das heißt, das Set von Spalten (oder Feldern), die sie enthält. Hier holen wir zunächst eine Referenz zur existierenden Datenbank von der
result-Eigenschaft des Ziels des Ereignisses (e.target.result), welches dasrequest-Objekt ist. Dies entspricht der Zeiledb = openRequest.result;imsuccess-Ereignishandler, aber wir müssen dies hier separat tun, weil derupgradeneeded-Ereignishandler (falls notwendig) vor demsuccess-Ereignishandler ausgeführt wird, was bedeutet, dass derdb-Wert nicht verfügbar wäre, wenn wir dies nicht tun.Wir verwenden dann
IDBDatabase.createObjectStore(), um einen neuen Objektspeicher innerhalb unserer geöffneten Datenbank namensnotes_oszu erstellen. Dies entspricht einer einzelnen Tabelle in einem herkömmlichen Datenbanksystem. Wir haben ihnnotesgenannt und auch einautoIncrement-Schlüsselfeld namensidangegeben — in jedem neuen Datensatz wird dies automatisch mit einem inkrementierten Wert gefüllt — der Entwickler muss dies nicht ausdrücklich einstellen. Daidder Schlüssel ist, wird das Feldidverwendet, um Datensätze eindeutig zu identifizieren, z.B. beim Löschen oder Anzeigen eines Datensatzes.Wir erstellen außerdem zwei andere Indizes (Felder) mithilfe der Methode
IDBObjectStore.createIndex():title(das einen Titel für jede Notiz enthält) undbody(das den Body-Text der Notiz enthält).
Mit diesem Datenbankschema eingerichtet werden, bei Beginn mit dem Hinzufügen von Datensätzen in die Datenbank, wird jeder als ein Objekt in etwa diesen Zeilen dargestellt:
{
"title": "Buy milk",
"body": "Need both cows milk and soy.",
"id": 8
}
Daten zur Datenbank hinzufügen
Schauen wir uns nun an, wie wir Datensätze zur Datenbank hinzufügen können. Dies erfolgt mithilfe des Formulars auf unserer Seite.
Fügen Sie unter Ihrem vorherigen Ereignishandler die folgende Zeile hinzu, welche einen submit Ereignishandler einrichtet, der eine Funktion namens addData() ausführt, wenn das Formular übermittelt wird (wenn der Submit-<button> gedrückt wird, was erfolgreich zur Formularübermittlung führt):
// Create a submit event handler so that when the form is submitted the addData() function is run
form.addEventListener("submit", addData);
Lassen Sie uns nun die Funktion addData() definieren. Fügen Sie diese unter Ihrer vorherigen Zeile hinzu:
// Define the addData() function
function addData(e) {
// prevent default - we don't want the form to submit in the conventional way
e.preventDefault();
// grab the values entered into the form fields and store them in an object ready for being inserted into the DB
const newItem = { title: titleInput.value, body: bodyInput.value };
// open a read/write db transaction, ready for adding the data
const transaction = db.transaction(["notes_os"], "readwrite");
// call an object store that's already been added to the database
const objectStore = transaction.objectStore("notes_os");
// Make a request to add our newItem object to the object store
const addRequest = objectStore.add(newItem);
addRequest.addEventListener("success", () => {
// Clear the form, ready for adding the next entry
titleInput.value = "";
bodyInput.value = "";
});
// Report on the success of the transaction completing, when everything is done
transaction.addEventListener("complete", () => {
console.log("Transaction completed: database modification finished.");
// update the display of data to show the newly added item, by running displayData() again.
displayData();
});
transaction.addEventListener("error", () =>
console.log("Transaction not opened due to error"),
);
}
Dies ist ziemlich komplex; zerlegen wir es:
- Wir führen
Event.preventDefault()am Ereignisobjekt aus, um zu verhindern, dass das Formular tatsächlich auf herkömmliche Weise übermittelt wird (dies würde zu einem Seiten-Reload führen und das Erlebnis verderben). - Wir erstellen ein Objekt, das einen Datensatz repräsentiert, der in die Datenbank eingegeben werden soll, und füllen es mit Werten aus den Formulareingaben. Beachten Sie, dass wir keinen
id-Wert explizit einfügen müssen — wie wir bereits erklärt haben, wird dies automatisch eingefügt. - Wir öffnen eine
readwrite-Transaktion gegen dennotes_osObjektspeicher mithilfe der MethodeIDBDatabase.transaction(). Dieses Transaktionsobjekt ermöglicht uns den Zugriff auf den Objektspeicher, damit wir etwas damit tun können, z.B. einen neuen Datensatz hinzufügen. - Wir greifen auf den Objektspeicher mithilfe der Methode
IDBTransaction.objectStore()zu und speichern das Ergebnis in der VariableobjectStore. - Wir fügen den neuen Datensatz mit
IDBObjectStore.add()zur Datenbank hinzu. Dies erzeugt ein Anfrageobjekt, ähnlich dem, was wir zuvor gesehen haben. - Wir fügen einer Reihe von Ereignishandlern zum
requestund dentransaction-Objekten hinzu, um Code zu kritischen Zeitpunkten im Lebenszyklus auszuführen. Sobald die Anfrage erfolgreich war, leeren wir die Formulareingaben, um die Eingabe der nächsten Notiz vorzubereiten. Sobald die Transaktion abgeschlossen ist, führen wir diedisplayData()-Funktion erneut aus, um die Anzeige der Notizen auf der Seite zu aktualisieren.
Daten anzeigen
Wir haben auf displayData() bereits zweimal in unserem Code verwiesen, daher sollten wir es wahrscheinlich definieren. Fügen Sie dies zu Ihrem Code hinzu, unterhalb der vorherigen Funktionsdefinition:
// Define the displayData() function
function displayData() {
// Here we empty the contents of the list element each time the display is updated
// If you didn't do this, you'd get duplicates listed each time a new note is added
while (list.firstChild) {
list.removeChild(list.firstChild);
}
// Open our object store and then get a cursor - which iterates through all the
// different data items in the store
const objectStore = db.transaction("notes_os").objectStore("notes_os");
objectStore.openCursor().addEventListener("success", (e) => {
// Get a reference to the cursor
const cursor = e.target.result;
// If there is still another data item to iterate through, keep running this code
if (cursor) {
// Create a list item, h3, and p to put each data item inside when displaying it
// structure the HTML fragment, and append it inside the list
const listItem = document.createElement("li");
const h3 = document.createElement("h3");
const para = document.createElement("p");
listItem.appendChild(h3);
listItem.appendChild(para);
list.appendChild(listItem);
// Put the data from the cursor inside the h3 and para
h3.textContent = cursor.value.title;
para.textContent = cursor.value.body;
// Store the ID of the data item inside an attribute on the listItem, so we know
// which item it corresponds to. This will be useful later when we want to delete items
listItem.setAttribute("data-note-id", cursor.value.id);
// Create a button and place it inside each listItem
const deleteBtn = document.createElement("button");
listItem.appendChild(deleteBtn);
deleteBtn.textContent = "Delete";
// Set an event handler so that when the button is clicked, the deleteItem()
// function is run
deleteBtn.addEventListener("click", deleteItem);
// Iterate to the next item in the cursor
cursor.continue();
} else {
// Again, if list item is empty, display a 'No notes stored' message
if (!list.firstChild) {
const listItem = document.createElement("li");
listItem.textContent = "No notes stored.";
list.appendChild(listItem);
}
// if there are no more cursor items to iterate through, say so
console.log("Notes all displayed");
}
});
}
Lassen Sie uns dies aufschlüsseln:
- Zuerst leeren wir den Inhalt des
<ul>Elements, bevor wir ihn mit dem aktualisierten Inhalt füllen. Wenn Sie dies nicht tun, würden Sie am Ende eine riesige Liste von duplizierten Inhalten erhalten, die bei jedem Update hinzugefügt würde. - Als Nächstes holen wir eine Referenz zum
notes_osObjektspeicher mithilfe vonIDBDatabase.transaction()undIDBTransaction.objectStore()wie wir es inaddData()gemacht haben, außer dass wir sie hier in einer einzigen Zeile verketten. - Der nächste Schritt ist die Verwendung der Methode
IDBObjectStore.openCursor(), um eine Anfrage für einen Cursor zu öffnen — ein Konstrukt, das zum Durchlaufen der Datensätze in einem Objektspeicher verwendet werden kann. Wir hängen einensuccess-Ereignishandler ans Ende dieser Zeile, um den Code prägnanter zu machen — wenn der Cursor erfolgreich zurückgegeben wird, wird der Handler ausgeführt. - Wir holen eine Referenz zum Cursor selbst (ein
IDBCursorObjekt) mithilfe vonconst cursor = e.target.result. - Dann überprüfen wir, ob der Cursor einen Datensatz aus dem Speicher enthält (
if (cursor){ }) — wenn ja, erstellen wir ein DOM-Fragment, füllen es mit den Daten des Datensatzes und fügen es in die Seite ein (innerhalb des<ul>Elements). Wir fügen auch einen Löschen-Button hinzu, der bei Klick diese Notiz löscht, indem wir diedeleteItem()-Funktion ausführen, die wir im nächsten Abschnitt ansehen werden. - Am Ende des
if-Blocks verwenden wir die MethodeIDBCursor.continue(), um den Cursor zum nächsten Datensatz im Speicher vorzurücken und den Inhalt desif-Blocks erneut auszuführen. Wenn es gibt einen weiteren Datensatz zum Iterieren, wird er in die Seite eingefügt undcontinue()erneut ausgeführt, und so weiter. - Wenn keine weiteren Datensätze zum Iterieren vorhanden sind, wird
cursorundefinedzurückgeben und daher wird derelse-Block anstelle desif-Blocks ausgeführt. Dieser Block überprüft, ob Notizen im<ul>eingefügt wurden — falls nicht, fügt er eine Nachricht hinzu, dass keine Notiz gespeichert wurde.
Eine Notiz löschen
Wie oben erwähnt, wird eine Notiz gelöscht, wenn der Löschen-Button einer Notiz gedrückt wird. Dies wird durch die deleteItem()-Funktion, die folgendermaßen aussieht, erreicht:
// Define the deleteItem() function
function deleteItem(e) {
// retrieve the name of the task we want to delete. We need
// to convert it to a number before trying to use it with IDB; IDB key
// values are type-sensitive.
const noteId = Number(e.target.parentNode.getAttribute("data-note-id"));
// open a database transaction and delete the task, finding it using the id we retrieved above
const transaction = db.transaction(["notes_os"], "readwrite");
const objectStore = transaction.objectStore("notes_os");
const deleteRequest = objectStore.delete(noteId);
// report that the data item has been deleted
transaction.addEventListener("complete", () => {
// delete the parent of the button
// which is the list item, so it is no longer displayed
e.target.parentNode.parentNode.removeChild(e.target.parentNode);
console.log(`Note ${noteId} deleted.`);
// Again, if list item is empty, display a 'No notes stored' message
if (!list.firstChild) {
const listItem = document.createElement("li");
listItem.textContent = "No notes stored.";
list.appendChild(listItem);
}
});
}
- Der ersteTeil davon benötigt etwas Erklärung — wir rufen die ID des zu löschenden Datensatzes mithilfe von
Number(e.target.parentNode.getAttribute('data-note-id'))ab — erinnern Sie sich daran, dass die ID des Datensatzes in einemdata-note-idAttribut auf der<li>gespeichert wurde, als sie zuerst angezeigt wurde. Wir müssen jedoch das Attribut durch das globale eingebauteNumber()Objekt übergeben, da es vom Datentyp Zeichenfolge ist und daher von der Datenbank, die eine Zahl erwartet, nicht erkannt würde. - Wir erhalten dann eine Referenz zum Objektspeicher, indem wir dasselbe Pattern verwenden, das wir zuvor gesehen haben, und verwenden die Methode
IDBObjectStore.delete(), um den Datensatz aus der Datenbank zu löschen, wobei wir die ID übergeben. - Wenn die Datenbanktransaktion abgeschlossen ist, löschen wir die Notiz-
<li>aus dem DOM und führen erneut die Überprüfung durch, ob das<ul>jetzt leer ist, und fügen eine Notiz entsprechend ein.
Das ist es! Ihr Beispiel sollte jetzt funktionieren.
Wenn Sie Probleme damit haben, können Sie es gerne mit unserem Live-Beispiel abgleichen (sehen Sie auch den Quellcode).
Komplexe Datenspeicherung über IndexedDB
Wie wir oben erwähnt haben, kann IndexedDB verwendet werden, um mehr als nur Textzeichenketten zu speichern. Sie können so ziemlich alles speichern, was Sie möchten, einschließlich komplexer Objekte wie Video- oder Image-Blobs. Und es ist nicht viel schwieriger zu erreichen als bei anderen Datentypen.
Um zu demonstrieren, wie man dies tut, haben wir ein weiteres Beispiel namens IndexedDB-Videothek geschrieben (sehen Sie es auch hier live laufen). Wenn Sie das Beispiel zum ersten Mal ausführen, lädt es alle Videos aus dem Netzwerk herunter, speichert sie in einer IndexedDB-Datenbank und zeigt die Videos dann in der Benutzeroberfläche innerhalb von <video> Elementen an. Wenn Sie es zum zweiten Mal ausführen, findet es die Videos in der Datenbank und ruft sie stattdessen von dort ab, bevor sie angezeigt werden — dies macht nachfolgende Ladungen viel schneller und bandbreitenschonender.
Gehen wir die interessantesten Teile des Beispiels durch. Wir werden nicht alles betrachten — vieles ist ähnlich wie im vorherigen Beispiel, und der Code ist gut kommentiert.
-
In diesem Beispiel haben wir die Namen der abzurufenden Videos in einem Array von Objekten gespeichert:
jsconst videos = [ { name: "crystal" }, { name: "elf" }, { name: "frog" }, { name: "monster" }, { name: "pig" }, { name: "rabbit" }, ]; -
Um zu beginnen, führen wir, sobald die Datenbank erfolgreich geöffnet ist, eine
init()-Funktion aus. Diese durchläuft die verschiedenen Videonamen und versucht, einen Datensatz zu laden, der durch jeden Namen aus dervideos-Datenbank identifiziert wird.Wenn jedes Video in der Datenbank gefunden wird (überprüft, indem geprüft wird, ob
request.resultzutrueausgewertet wird — wenn der Datensatz nicht vorhanden ist, wird erundefinedsein), werden seine Videodateien (gespeichert als Blobs) und der Videoname direkt an diedisplayVideo()-Funktion übergeben, um sie in der Benutzeroberfläche zu platzieren. Wenn nicht, wird der Videoname an diefetchVideoFromNetwork()-Funktion übergeben, um, sie ahnen es, das Video aus dem Netzwerk abzurufen.jsfunction init() { // Loop through the video names one by one for (const video of videos) { // Open transaction, get object store, and get() each video by name const objectStore = db.transaction("videos_os").objectStore("videos_os"); const request = objectStore.get(video.name); request.addEventListener("success", () => { // If the result exists in the database (is not undefined) if (request.result) { // Grab the videos from IDB and display them using displayVideo() console.log("taking videos from IDB"); displayVideo( request.result.mp4, request.result.webm, request.result.name, ); } else { // Fetch the videos from the network fetchVideoFromNetwork(video); } }); } } -
Der folgende Ausschnitt stammt aus
fetchVideoFromNetwork()— hier rufen wir MP4- und WebM-Versionen des Videos mithilfe von zwei separatenfetch()-Anfragen ab. Wir verwenden dann die MethodeResponse.blob(), um den Body jeder Antwort als Blob zu extrahieren, was uns eine Objektrepräsentation der Videos gibt, die später gespeichert und angezeigt werden kann.Wir haben hier ein Problem — diese beiden Anfragen sind beide asynchron, aber wir möchten das Video erst anzeigen oder speichern, wenn beide Versprechen erfüllt wurden. Zum Glück gibt es eine eingebaute Methode, die solche Probleme behandelt —
Promise.all(). Diese nimmt ein Argument — Referenzen auf alle einzelnen Versprechen, die Sie auf Erfüllung prüfen möchten, in einem Array platziert — und gibt ein Versprechen zurück, das erfüllt wird, wenn alle einzelnen Versprechen erfüllt sind.Im
then()-Handler für dieses Versprechen rufen wir diedisplayVideo()-Funktion wie zuvor auf, um die Videos in der Benutzeroberfläche anzuzeigen, dann rufen wir auch diestoreVideo()-Funktion auf, um diese Videos in der Datenbank zu speichern.js// Fetch the MP4 and WebM versions of the video using the fetch() function, // then expose their response bodies as blobs const mp4Blob = fetch(`videos/${video.name}.mp4`).then((response) => response.blob(), ); const webmBlob = fetch(`videos/${video.name}.webm`).then((response) => response.blob(), ); // Only run the next code when both promises have fulfilled Promise.all([mp4Blob, webmBlob]).then((values) => { // display the video fetched from the network with displayVideo() displayVideo(values[0], values[1], video.name); // store it in the IDB using storeVideo() storeVideo(values[0], values[1], video.name); }); -
Lassen Sie uns zuerst auf
storeVideo()schauen. Dies ist sehr ähnlich dem Muster, das Sie im vorherigen Beispiel zum Hinzufügen von Daten zur Datenbank gesehen haben — wir öffnen einereadwrite-Transaktion und holen eine Referenz zu unseremvideos_osObjektspeicher, erstellen ein Objekt, das den Datensatz zur Datenbank repräsentiert, dann fügen wir es mithilfe vonIDBObjectStore.add()hinzu.js// Define the storeVideo() function function storeVideo(mp4, webm, name) { // Open transaction, get object store; make it a readwrite so we can write to the IDB const objectStore = db .transaction(["videos_os"], "readwrite") .objectStore("videos_os"); // Add the record to the IDB using add() const request = objectStore.add({ mp4, webm, name }); request.addEventListener("success", () => console.log("Record addition attempt finished"), ); request.addEventListener("error", () => console.error(request.error)); } -
Schließlich haben wir
displayVideo(), das die DOM-Elemente erstellt, die benötigt werden, um das Video in der Benutzeroberfläche einzufügen, und sie dann in die Seite einfügt. Das Interessanteste daran sind die im nächsten Abschnitt gezeigten — um unsere Videoklumpen in einem<video>-Element tatsächlich anzuzeigen, müssen wir Objekt-URLs (interne URLs, die auf die im Arbeitsspeicher gespeicherten Videoklumpen zeigen) mithilfe der MethodeURL.createObjectURL()erstellen. Sobald das abgeschlossen ist, können wir die Objekt-URLs als Werte dersrc-Attribute unserer<source>Elemente setzen und das funktioniert einwandfrei.js// Define the displayVideo() function function displayVideo(mp4Blob, webmBlob, title) { // Create object URLs out of the blobs const mp4URL = URL.createObjectURL(mp4Blob); const webmURL = URL.createObjectURL(webmBlob); // Create DOM elements to embed video in the page const article = document.createElement("article"); const h2 = document.createElement("h2"); h2.textContent = title; const video = document.createElement("video"); video.controls = true; const source1 = document.createElement("source"); source1.src = mp4URL; source1.type = "video/mp4"; const source2 = document.createElement("source"); source2.src = webmURL; source2.type = "video/webm"; // Embed DOM elements into page section.appendChild(article); article.appendChild(h2); article.appendChild(video); video.appendChild(source1); video.appendChild(source2); }
Offline-Asset-Speicherung
Das obige Beispiel zeigt bereits, wie man eine App erstellt, die große Assets in einer IndexedDB-Datenbank speichert, was die Notwendigkeit, sie mehr als einmal herunterzuladen, vermeidet. Dies ist bereits eine große Verbesserung des Benutzererlebnisses, aber es fehlt noch eine Sache — die Haupt-HTML-, CSS- und JavaScript-Dateien müssen immer noch bei jedem Zugriff auf die Site heruntergeladen werden, was bedeutet, dass sie nicht ohne Netzwerkverbindung funktionieren wird.

Dies ist, wo Service Worker und die eng verbundene Cache API ins Spiel kommen.
Ein Service Worker ist eine JavaScript-Datei, die bei Zugriff durch einen Browser gegen einen bestimmten Ursprung (Website oder Teil einer Website an einer bestimmten Domäne) registriert wird. Wenn registriert, kann er Seiten kontrollieren, die an diesem Ursprung verfügbar sind. Er tut dies, indem er zwischen einer geladenen Seite und dem Netzwerk sitzt und Netzwerkrequests abfängt, die an diesen Ursprung gerichtet sind.
Wenn er eine Anfrage abfängt, kann er alles gewünschte damit tun (siehe Ideen für Anwendungsfälle), aber das klassische Beispiel ist das Speichern von Netzwerkantworten offline und dann diese als Antwort auf eine Anfrage anstelle der Antworten aus dem Netzwerk bereitzustellen. Effektiv ermöglicht es Ihnen, eine Website vollständig offline arbeiten zu lassen.
Die Cache-API ist ein weiteres Client-seitiges Speichermedium mit einem Unterschied — sie ist dafür konzipiert, HTTP-Antworten zu speichern und arbeitet sehr gut mit Service Workern zusammen.
Ein Service Worker Beispiel
Schauen wir uns ein Beispiel an, um Ihnen eine Vorstellung davon zu geben, wie das aussehen könnte. Wir haben eine weitere Version des Videoarchiv-Beispiels erstellt, das wir im vorherigen Abschnitt gesehen haben — dieses funktioniert identisch, außer dass es auch die HTML-, CSS- und JavaScript-Dateien über die Cache-API über einen Service Worker speichert, sodass das Beispiel offline ausgeführt werden kann!
Sehen Sie sich IndexedDB-Videothek mit Service Worker live laufend an und sehen Sie sich auch den Quellcode an.
Den Service Worker registrieren
Das erste, was zu beachten ist, ist, dass es ein extra Stück Code in der Haupt-JavaScript-Datei gibt (siehe index.js). Zuerst führen wir einen Feature-Detection-Test durch, um zu sehen, ob das serviceWorker-Mitglied im Navigator-Objekt verfügbar ist. Wenn dies true zurückgibt, dann wissen wir, dass zumindest die Grundlagen von Service Workern unterstützt werden. Innerhalb hiervon verwenden wir die Methode ServiceWorkerContainer.register(), um einen Service Worker, der sich in der sw.js-Datei befindet, gegen den Ursprung, an dem er sich befindet, zu registrieren, sodass er in der Lage ist, Seiten im gleichen Verzeichnis wie er oder in Unterverzeichnissen zu kontrollieren. Wenn sein Versprechen erfüllt ist, wird der Service Worker als registriert angesehen.
// Register service worker to control making site work offline
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register(
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js",
)
.then(() => console.log("Service Worker Registered"));
}
Hinweis:
Der angegebene Pfad zur sw.js-Datei ist relativ zum Seitenursprung, nicht zu der JavaScript-Datei, die den Code enthält. Der Service Worker befindet sich bei https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js. Der Ursprung ist https://mdn.github.io, und daher muss der angegebene Pfad /learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js sein. Wenn Sie dieses Beispiel auf Ihrem eigenen Server hosten möchten, müssten Sie dies entsprechend ändern. Dies ist etwas verwirrend, aber es muss aus Sicherheitsgründen so funktionieren.
Den Service Worker installieren
Beim nächsten Mal, wenn eine Seite unter der Kontrolle des Service Workers abgerufen wird (z.B. wenn das Beispiel neu geladen wird), wird der Service Worker gegen diese Seite installiert, was bedeutet, dass er beginnen wird, sie zu kontrollieren. Beim Auftreten dieser Instanz wird ein Installationsereignis gegen den Service Worker ausgelöst; sie können Code innerhalb des Service Workers selbst schreiben, der auf die Installation reagiert.
Schauen wir uns ein Beispiel an, in der sw.js-Datei (dem Service Worker). Sie werden sehen, dass der Installations-Listener gegen self registriert ist. Dieses self-Schlüsselwort ist eine Möglichkeit, auf den globalen Gültigkeitsbereich des Service Workers von innerhalb der Service Worker-Datei zu verweisen.
Innerhalb des Installations-Handlers verwenden wir die Methode ExtendableEvent.waitUntil(), die im Ereignisobjekt verfügbar ist, um darauf hinzuweisen, dass der Browser die Installation des Service Workers erst nach erfolgreicher Erfüllung des Versprechens abschließen soll.
Hier sehen wir die Cache-API in Aktion. Wir verwenden die Methode CacheStorage.open(), um ein neues Cache-Objekt zu öffnen, in dem Antworten gespeichert werden können (ähnlich einem IndexedDB-Objektspeicher). Dieses Versprechen wird mit einem Cache-Objekt erfüllt, das den video-store-Cache repräsentiert. Wir verwenden dann die Methode Cache.addAll(), um eine Reihe von Assets abzurufen und ihre Antworten in den Cache hinzuzufügen.
self.addEventListener("install", (e) => {
e.waitUntil(
caches
.open("video-store")
.then((cache) =>
cache.addAll([
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/",
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.html",
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js",
"/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/style.css",
]),
),
);
});
Das ist es bis jetzt, die Installation ist abgeschlossen.
Auf weitere Anfragen reagieren
Mit dem Service Worker, der registriert und gegen unsere HTML-Seite installiert ist, und den relevanten Assets, die alle unserem Cache hinzugefügt wurden, sind wir fast bereit zu gehen. Es bleibt nur noch eine Sache zu tun: Schreiben Sie ein wenig Code, um auf weitere Netzwerkanfragen zu antworten.
Das ist es, was der zweite Teil des Codes in sw.js tut. Wir fügen einen weiteren Listener zum globalen Gültigkeitsbereich des Service Workers hinzu, der die Handler-Funktion ausführt, wenn das fetch-Ereignis ausgelöst wird. Dies geschieht, wann immer der Browser eine Anfrage für ein Asset im Verzeichnis, in dem der Service Worker registriert ist, macht.
Innerhalb des Handlers loggen wir zuerst die URL des angeforderten Assets. Wir geben dann eine benutzerdefinierte Antwort auf die Anfrage mithilfe der Methode FetchEvent.respondWith() zurück.
Innerhalb dieses Blocks verwenden wir CacheStorage.match(), um zu überprüfen, ob eine übereinstimmende Anfrage (d.h. eine, die die URL übereinstimmt) in einem Cache gefunden werden kann. Dieses Versprechen wird mit der übereinstimmenden Antwort erfüllt, falls eine Übereinstimmung gefunden wird, oder undefined, falls keine gefunden wird.
Wenn eine Übereinstimmung gefunden wird, geben wir sie als benutzerdefinierte Antwort zurück. Falls nicht, holen wir die Antwort aus dem Netzwerk und geben stattdessen diese zurück.
self.addEventListener("fetch", (e) => {
console.log(e.request.url);
e.respondWith(
caches.match(e.request).then((response) => response || fetch(e.request)),
);
});
Das war unser Service Worker. Es gibt noch viel mehr, was man mit ihnen tun kann – für viel mehr Details siehe das Service Worker Kochbuch. Vielen Dank an Paul Kinlan für seinen Artikel Adding a Service Worker and Offline into your Web App, der dieses Beispiel inspiriert hat.
Das Beispiel offline testen
Um unser Service Worker Beispiel zu testen, müssen Sie es ein paar Mal laden, um sicherzustellen, dass es registriert ist. Sobald dies geschehen ist, können Sie:
- Versuchen Sie, Ihre Netzwerkverbindung zu trennen/Ihr WLAN auszuschalten.
- Wählen Sie Datei > Offline arbeiten wenn Sie Firefox verwenden.
- Gehen Sie zu den Devtools, dann wählen Sie Application > Service Workers, dann das Offline Kontrollkästchen aktivieren, wenn Sie Chrome verwenden.
Wenn Sie Ihre Beispielseite erneut aktualisieren, sollten Sie sehen, dass sie weiterhin einwandfrei geladen wird. Alles wird offline gespeichert — die Seiten-Assets in einem Cache und die Videos in einer IndexedDB-Datenbank.
Zusammenfassung
Das war's vorerst. Wir hoffen, dass Sie unseren Überblick über Technologien zur client-seitigen Speicherung nützlich fanden.