Ein Leitfaden zu den verschiedenen Arten von Browser-Speicher

Avatar of Ido Shamun
Ido Shamun am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

In der Backend-Entwicklung ist Speicher ein üblicher Teil der Arbeit. Anwendungsdaten werden in Datenbanken gespeichert, Dateien in Objektspeichern, transiente Daten in Caches... es gibt scheinbar endlose Möglichkeiten, beliebige Daten zu speichern. Aber Datenspeicherung ist nicht auf das Backend beschränkt. Das Frontend (der Browser) ist ebenfalls mit vielen Optionen zur Datenspeicherung ausgestattet. Wir können die Anwendungsleistung verbessern, Benutzereinstellungen speichern, den Anwendungsstatus über mehrere Sitzungen oder sogar verschiedene Computer hinweg beibehalten, indem wir diesen Speicher nutzen.

In diesem Artikel gehen wir die verschiedenen Möglichkeiten zur Datenspeicherung im Browser durch. Wir behandeln drei Anwendungsfälle für jede Methode, um die Vor- und Nachteile zu verstehen. Am Ende können Sie entscheiden, welcher Speicher am besten zu Ihrem Anwendungsfall passt. Fangen wir also an!

Die localStorage API

localStorage ist eine der beliebtesten Speicheroptionen im Browser und für viele Entwickler die erste Wahl. Die Daten werden sitzungsübergreifend gespeichert, niemals mit dem Server geteilt und sind für alle Seiten unter demselben Protokoll und derselben Domain verfügbar. Der Speicherplatz ist auf ca. 5 MB begrenzt.

Überraschenderweise empfiehlt das Google Chrome Team die Verwendung dieser Option nicht, da sie den Haupt-Thread blockiert und für Web-Worker und Service-Worker nicht zugänglich ist. Sie haben ein Experiment namens KV Storage als bessere Version gestartet, aber das war nur ein Versuch, der bisher noch keine Früchte getragen zu haben scheint.

Die localStorage API ist als window.localStorage verfügbar und kann nur UTF-16-Zeichenketten speichern. Wir müssen darauf achten, Daten vor dem Speichern in localStorage in Zeichenketten umzuwandeln. Die drei Hauptfunktionen sind

  • setItem('key', 'value')
  • getItem('key')
  • removeItem('key')

Sie sind alle synchron, was die Arbeit damit vereinfacht, aber sie blockieren den Haupt-Thread.

Es ist erwähnenswert, dass localStorage einen Zwilling namens sessionStorage hat. Der einzige Unterschied besteht darin, dass die in sessionStorage gespeicherten Daten nur für die aktuelle Sitzung gültig sind, die API ist jedoch dieselbe.

Sehen wir uns das in Aktion an. Das erste Beispiel zeigt, wie localStorage zum Speichern der Benutzereinstellungen verwendet wird. In unserem Fall handelt es sich um eine boolesche Eigenschaft, die das dunkle Theme unserer Website ein- oder ausschaltet.

Sie können die Checkbox aktivieren und die Seite aktualisieren, um zu sehen, dass der Zustand sitzungsübergreifend gespeichert wird. Schauen Sie sich die Funktionen save und load an, um zu sehen, wie ich den Wert in eine Zeichenkette umwandle und wie ich ihn parsiere. Es ist wichtig zu bedenken, dass wir nur Zeichenketten speichern können.

Dieses zweite Beispiel lädt Pokémon-Namen von der PokéAPI.

Wir senden eine GET-Anfrage mit fetch und listen alle Namen in einem ul-Element auf. Nach Erhalt der Antwort cachen wir sie im localStorage, damit unser nächster Besuch viel schneller sein kann oder sogar offline funktioniert. Wir müssen JSON.stringify verwenden, um die Daten in eine Zeichenkette umzuwandeln, und JSON.parse, um sie aus dem Cache zu lesen.

In diesem letzten Beispiel demonstriere ich einen Anwendungsfall, bei dem der Benutzer verschiedene Pokémon-Seiten durchsuchen kann und die aktuelle Seite für zukünftige Besuche gespeichert wird.

Das Problem mit localStorage ist in diesem Fall, dass der Status lokal gespeichert wird. Dieses Verhalten erlaubt es uns nicht, die gewünschte Seite mit Freunden zu teilen. Später werden wir sehen, wie wir dieses Problem lösen können.

Wir werden diese drei Beispiele auch für die nächsten Speicheroptionen verwenden. Ich habe die Pens geforkt und nur die relevanten Funktionen geändert. Das allgemeine Gerüst ist für alle Methoden gleich.

Die IndexedDB API

IndexedDB ist eine moderne Speicherlösung im Browser. Sie kann eine erhebliche Menge an strukturierten Daten speichern - sogar Dateien und Blobs. Wie jede Datenbank indiziert IndexedDB die Daten für eine effiziente Abfrageausführung. Die Verwendung von IndexedDB ist komplexer. Wir müssen eine Datenbank, Tabellen erstellen und Transaktionen verwenden.

Im Vergleich zu localStorage erfordert IndexedDB deutlich mehr Code. In den Beispielen verwende ich die native API mit einem Promise-Wrapper, aber ich empfehle dringend die Verwendung von Drittanbieter-Bibliotheken, um Ihnen zu helfen. Meine Empfehlung ist localForage, da es die gleiche localStorage API verwendet, sie aber progressiv verbessert implementiert, d.h. wenn Ihr Browser IndexedDB unterstützt, wird er es verwenden; und wenn nicht, wird er auf localStorage zurückfallen.

Lassen Sie uns programmieren und zu unserem Anwendungsfall für Benutzereinstellungen übergehen!

idb ist der Promise-Wrapper, den wir anstelle der Arbeit mit einer Low-Level-Events-basierten API verwenden. Sie sind fast identisch, also keine Sorge. Das erste, was man bemerken sollte, ist, dass jeder Zugriff auf die Datenbank asynchron ist, was bedeutet, dass wir den Haupt-Thread nicht blockieren. Im Vergleich zu localStorage ist dies ein großer Vorteil.

Wir müssen eine Verbindung zu unserer Datenbank öffnen, damit sie für das Lesen und Schreiben in der gesamten App verfügbar ist. Wir geben unserer Datenbank einen Namen, my-db, eine Schemanummer, 1, und eine Update-Funktion, um Änderungen zwischen Versionen anzuwenden. Dies ähnelt stark Datenbankmigrationen. Unser Datenbankschema ist einfach: nur ein Objektspeicher, preferences. Ein Objektspeicher ist das Äquivalent einer SQL-Tabelle. Um in die Datenbank zu schreiben oder aus ihr zu lesen, müssen wir Transaktionen verwenden. Das ist der mühsame Teil der Verwendung von IndexedDB. Werfen Sie einen Blick auf die neuen Funktionen save und load in der Demo.

Zweifellos hat IndexedDB viel mehr Overhead und die Lernkurve ist steiler im Vergleich zu localStorage. Für Key-Value-Fälle kann es sinnvoller sein, localStorage oder eine Drittanbieterbibliothek zu verwenden, die uns produktiver macht.

Anwendungsdaten, wie in unserem Pokémon-Beispiel, sind die Stärke von IndexedDB. Sie können Hunderte von Megabyte und sogar mehr in dieser Datenbank speichern. Sie können alle Pokémon in IndexedDB speichern und sie offline verfügbar und sogar indiziert haben! Dies ist definitiv die richtige Wahl für die Speicherung von App-Daten.

Ich habe die Implementierung des dritten Beispiels übersprungen, da IndexedDB in diesem Fall keinen Unterschied im Vergleich zu localStorage macht. Selbst mit IndexedDB wird der Benutzer die ausgewählte Seite immer noch nicht mit anderen teilen oder sie für die zukünftige Verwendung bookmarken. Beides ist für diesen Anwendungsfall nicht geeignet.

Cookies

Die Verwendung von Cookies ist eine einzigartige Speicheroption. Es ist der einzige Speicher, der auch mit dem Server geteilt wird. Cookies werden als Teil jeder Anfrage gesendet. Das kann geschehen, wenn der Benutzer durch Seiten unserer App browsen oder wenn der Benutzer Ajax-Anfragen sendet. Dies ermöglicht es uns, einen gemeinsamen Zustand zwischen Client und Server zu erstellen und auch einen Zustand zwischen mehreren Anwendungen in verschiedenen Subdomains zu teilen. Dies ist mit anderen in diesem Artikel beschriebenen Speicheroptionen nicht möglich. Ein Nachteil: Cookies werden bei jeder Anfrage gesendet, was bedeutet, dass wir unsere Cookies klein halten müssen, um eine angemessene Anfragedatenmenge zu gewährleisten.

Der häufigste Verwendungszweck für Cookies ist die Authentifizierung, die außerhalb des Rahmens dieses Artikels liegt. Genau wie localStorage können Cookies nur Zeichenketten speichern. Die Cookies werden zu einer semikolon-getrennten Zeichenkette verkettet und im Cookie-Header der Anfrage gesendet. Sie können für jeden Cookie viele Attribute festlegen, wie z. B. Ablaufdatum, zulässige Domänen, zulässige Seiten und vieles mehr.

In den Beispielen zeige ich, wie man die Cookies clientseitig manipuliert, aber es ist auch möglich, sie serverseitig in Ihrer Anwendung zu ändern.

Das Speichern von Benutzereinstellungen in einem Cookie kann sinnvoll sein, wenn der Server sie irgendwie nutzen kann. Im Fall des Themas könnte der Server beispielsweise die relevante CSS-Datei liefern und die potenzielle Bundle-Größe reduzieren (falls wir serverseitiges Rendering betreiben). Ein weiterer Anwendungsfall könnte darin bestehen, diese Einstellungen zwischen mehreren Subdomain-Apps ohne Datenbank zu teilen.

Das Lesen und Schreiben von Cookies mit JavaScript ist nicht so einfach, wie man denken könnte. Um einen neuen Cookie zu speichern, müssen Sie document.cookie setzen - schauen Sie sich die Funktion save im obigen Beispiel an. Ich habe den Cookie dark_theme gesetzt und ein Attribut max-age hinzugefügt, um sicherzustellen, dass er nicht abläuft, wenn die Registerkarte geschlossen wird. Außerdem füge ich die Attribute SameSite und Secure hinzu. Diese sind notwendig, da CodePen iframes zur Ausführung der Beispiele verwendet, aber Sie werden sie in den meisten Fällen nicht benötigen. Das Lesen eines Cookies erfordert das Parsen der Cookie-Zeichenkette.

Eine Cookie-Zeichenkette sieht so aus

key1=value1;key2=value2;key3=value3

Also müssen wir zuerst die Zeichenkette nach Semikolon aufteilen. Jetzt haben wir ein Array von Cookies im Format key1=value1, also müssen wir das richtige Element im Array finden. Am Ende teilen wir nach dem Gleichheitszeichen auf und erhalten das letzte Element im neuen Array. Ein bisschen mühsam, aber sobald Sie die Funktion getCookie implementiert haben (oder sie aus meinem Beispiel kopiert haben :P) können Sie sie vergessen.

Das Speichern von Anwendungsdaten in einem Cookie kann eine schlechte Idee sein! Es wird die Anfragedatenmenge drastisch erhöhen und die Anwendungsleistung verringern. Außerdem kann der Server von diesen Informationen nicht profitieren, da es sich um eine veraltete Version der Informationen handelt, die er bereits in seiner Datenbank hat. Wenn Sie Cookies verwenden, stellen Sie sicher, dass sie klein bleiben.

Das Paginierungsbeispiel ist auch für Cookies nicht gut geeignet, genau wie localStorage und IndexedDB. Die aktuelle Seite ist ein temporärer Zustand, den wir mit anderen teilen möchten, und keine dieser Methoden erreicht dies.

URL-Speicherung

URLs sind nicht per se ein Speicher, aber sie sind eine großartige Möglichkeit, einen teilbaren Zustand zu erstellen. In der Praxis bedeutet dies, Abfrageparameter zur aktuellen URL hinzuzufügen, mit denen der aktuelle Zustand wiederhergestellt werden kann. Das beste Beispiel wären Suchanfragen und Filter. Wenn wir den Begriff flexbox auf CSS-Tricks suchen, wird die URL zu https://css-tricks.de/?s=flexbox aktualisiert. Sehen Sie, wie einfach es ist, eine Suchanfrage zu teilen, sobald wir die URL verwenden? Ein weiterer Vorteil ist, dass Sie einfach die Aktualisierungstaste drücken können, um neuere Ergebnisse Ihrer Abfrage zu erhalten oder sie sogar zu bookmarken.

Wir können nur Zeichenketten in der URL speichern, und ihre maximale Länge ist begrenzt, sodass wir nicht viel Platz haben. Wir müssen unseren Zustand klein halten. Niemand mag lange und einschüchternde URLs.

Auch hier verwendet CodePen iframes zur Ausführung der Beispiele, sodass Sie die URL-Änderungen nicht sehen können. Machen Sie sich keine Sorgen, denn alle Teile sind vorhanden, sodass Sie sie überall verwenden können.

Wir können auf die Abfragezeichenkette über window.location.search zugreifen und, glücklicherweise, sie mit der Klasse URLSearchParams parsen. Es ist keine Notwendigkeit mehr für komplexes String-Parsing. Wenn wir den aktuellen Wert lesen wollen, können wir die Funktion get verwenden. Wenn wir schreiben wollen, können wir set verwenden. Es reicht nicht aus, nur den Wert zu setzen; wir müssen auch die URL aktualisieren. Dies kann mit history.pushState oder history.replaceState erfolgen, abhängig vom Verhalten, das wir erreichen wollen.

Ich würde nicht empfehlen, Benutzereinstellungen in der URL zu speichern, da wir diesen Zustand zu jeder besuchten URL hinzufügen müssten und wir ihn nicht garantieren können; zum Beispiel, wenn der Benutzer auf einen Link von Google Search klickt.

Genau wie bei Cookies können wir keine Anwendungsdaten in der URL speichern, da wir nur begrenzten Platz haben. Und selbst wenn wir es schaffen, sie zu speichern, wird die URL lang und nicht einladend zum Klicken sein. Könnte fast wie ein Phishing-Angriff aussehen.

Genau wie bei unserem Paginierungsbeispiel ist der temporäre Anwendungsstatus die beste Wahl für die URL-Abfragezeichenkette. Wiederum können Sie die URL-Änderungen nicht sehen, aber die URL wird bei jedem Klick auf eine Seite mit dem Abfrageparameter ?page=x aktualisiert. Wenn die Webseite geladen wird, sucht sie nach diesem Abfrageparameter und ruft die entsprechende Seite ab. Jetzt können wir diese URL mit unseren Freunden teilen, damit sie unsere Lieblings-Pokémon genießen können.

Cache API

Die Cache API ist ein Speicher auf Netzwerkebene. Sie wird zum Cachen von Netzwerkanfragen und ihren Antworten verwendet. Die Cache API passt perfekt zu Service Workern. Ein Service Worker kann jede Netzwerkanfrage abfangen und mit der Cache API sowohl die Anfragen als auch die Antworten problemlos cachen. Der Service Worker kann auch einen vorhandenen Cache-Eintrag als Netzwerkantwort zurückgeben, anstatt ihn vom Server abzurufen. Dadurch können Sie die Netzwerkladezeiten reduzieren und Ihre Anwendung auch offline funktionieren lassen. Ursprünglich wurde sie für Service Worker entwickelt, aber in modernen Browsern ist die Cache API auch im Fenster-, iframe- und Worker-Kontext verfügbar. Es ist eine sehr leistungsstarke API, die die Benutzererfahrung der Anwendung drastisch verbessern kann.

Genau wie IndexedDB ist der Speicher der Cache API nicht begrenzt und Sie können Hunderte von Megabyte und bei Bedarf sogar mehr speichern. Die API ist asynchron, sodass sie Ihren Haupt-Thread nicht blockiert. Und sie ist über die globale Eigenschaft caches zugänglich.

Um mehr über die Cache API zu erfahren, hat das Google Chrome Team ein großartiges Tutorial erstellt.

Chris hat einen fantastischen Pen mit einem praktischen Beispiel für die Kombination von Service Workern und der Cache API erstellt.

Bonus: Browsererweiterung

Wenn Sie eine Browsererweiterung erstellen, haben Sie eine weitere Option zur Speicherung Ihrer Daten. Ich habe sie entdeckt, während ich an meiner Erweiterung, daily.dev, gearbeitet habe. Sie ist über chrome.storage oder browser.storage verfügbar, wenn Sie Mozillas Polyfill verwenden. Stellen Sie sicher, dass Sie eine Speicherberechtigung in Ihrem Manifest anfordern, um Zugriff zu erhalten.

Es gibt zwei Arten von Speicheroptionen: lokal und synchronisiert. Der lokale Speicher ist selbsterklärend; er bedeutet, dass er nicht geteilt und lokal aufbewahrt wird. Der synchrone Speicher wird als Teil des Google-Kontos synchronisiert, und überall dort, wo Sie die Erweiterung mit demselben Konto installieren, wird dieser Speicher synchronisiert. Eine ziemlich coole Funktion, wenn Sie mich fragen. Beide haben die gleiche API, so dass es super einfach ist, bei Bedarf zwischen ihnen zu wechseln. Es ist ein asynchroner Speicher, sodass er den Haupt-Thread nicht blockiert, wie localStorage. Leider kann ich keine Demo für diese Speicheroption erstellen, da sie eine Browsererweiterung erfordert, aber sie ist ziemlich einfach zu verwenden und fast wie localStorage. Weitere Informationen zur genauen Implementierung finden Sie in der Chrome-Dokumentation.

Fazit

Der Browser bietet viele Optionen, die wir zur Speicherung unserer Daten nutzen können. Nach den Ratschlägen des Chrome-Teams sollte unsere erste Wahl IndexedDB sein. Es ist ein asynchroner Speicher mit genügend Platz, um alles zu speichern, was wir wollen. localStorage wird nicht empfohlen, ist aber einfacher zu verwenden als IndexedDB. Cookies sind eine großartige Möglichkeit, den Client-Status mit dem Server zu teilen, werden aber hauptsächlich für die Authentifizierung verwendet.

Wenn Sie Seiten mit einem teilbaren Status erstellen möchten, wie z. B. eine Suchseite, verwenden Sie die Abfragezeichenkette der URL, um diese Informationen zu speichern. Schließlich, wenn Sie eine Erweiterung erstellen, stellen Sie sicher, dass Sie sich über chrome.storage informieren.