Wie wir aus dem Kapitel „Garbage Collection“ wissen, behält die JavaScript-Engine einen Wert im Speicher, solange dieser „erreichbar“ und potenziell verwendbar ist.
Zum Beispiel:
let john = { name: "John" }; // Auf das Objekt kann zugegriffen werden, John ist die Referenz darauf // Referenz überschreiben john = null; // Das Objekt wird aus dem Speicher entfernt
Normalerweise gelten Eigenschaften eines Objekts oder Elemente eines Arrays oder einer anderen Datenstruktur als erreichbar und werden im Speicher gehalten, während sich diese Datenstruktur im Speicher befindet.
Wenn wir beispielsweise ein Objekt in ein Array einfügen, bleibt das Objekt ebenfalls lebendig, solange das Array aktiv ist, selbst wenn keine anderen Verweise darauf vorhanden sind.
So was:
let john = { name: "John" }; let array = [ john ]; john = null; // Referenz überschreiben // Das zuvor von John referenzierte Objekt wird im Array gespeichert // Daher wird es nicht im Garbage Collection erfasst // wir können es als Array[0] bekommen
Wenn wir ein Objekt als Schlüssel in einer regulären Map
verwenden, existiert ähnlich wie bei der Map
auch dieses Objekt. Es belegt Speicher und wird möglicherweise nicht durch Garbage Collection erfasst.
Zum Beispiel:
let john = { name: "John" }; let map = new Map(); map.set(john, "..."); john = null; // Referenz überschreiben // John wird in der Karte gespeichert, // wir können es bekommen, indem wir map.keys() verwenden
WeakMap
unterscheidet sich in dieser Hinsicht grundlegend. Es verhindert nicht die Speicherbereinigung von Schlüsselobjekten.
Sehen wir uns anhand von Beispielen an, was es bedeutet.
Der erste Unterschied zwischen Map
und WeakMap
besteht darin, dass Schlüssel Objekte und keine primitiven Werte sein müssen:
let schwachMap = new WeakMap(); sei obj = {}; schwachMap.set(obj, "ok"); // funktioniert gut (Objektschlüssel) // kann keinen String als Schlüssel verwenden schwachMap.set("test", "Whoops"); // Fehler, da „test“ kein Objekt ist
Wenn wir nun ein Objekt als Schlüssel darin verwenden und es keine anderen Verweise auf dieses Objekt gibt, wird es automatisch aus dem Speicher (und aus der Karte) entfernt.
let john = { name: "John" }; let schwachMap = new WeakMap(); schwachMap.set(john, "..."); john = null; // Referenz überschreiben // John wird aus dem Speicher entfernt!
Vergleichen Sie es mit dem regulären Map
oben. Wenn john
nun nur als Schlüssel von WeakMap
existiert, wird er automatisch aus der Karte (und dem Speicher) gelöscht.
WeakMap
unterstützt keine Iteration und die Methoden keys()
, values()
und entries()
, daher gibt es keine Möglichkeit, alle Schlüssel oder Werte daraus abzurufen.
WeakMap
verfügt nur über die folgenden Methoden:
weakMap.set(key, value)
weakMap.get(key)
weakMap.delete(key)
weakMap.has(key)
Warum eine solche Einschränkung? Das hat technische Gründe. Wenn ein Objekt alle anderen Referenzen verloren hat (wie john
im obigen Code), wird es automatisch in den Garbage Collection-Speicher verschoben. Technisch gesehen ist jedoch nicht genau festgelegt, wann die Bereinigung erfolgt .
Das entscheidet die JavaScript-Engine. Es kann sich dafür entscheiden, die Speicherbereinigung sofort durchzuführen oder zu warten und die Bereinigung später durchzuführen, wenn weitere Löschungen erfolgen. Technisch gesehen ist die aktuelle Elementanzahl einer WeakMap
also nicht bekannt. Möglicherweise hat der Motor es gereinigt oder auch nicht oder nur teilweise. Aus diesem Grund werden Methoden, die auf alle Schlüssel/Werte zugreifen, nicht unterstützt.
Wo brauchen wir nun eine solche Datenstruktur?
Der Haupteinsatzbereich von WeakMap
ist die zusätzliche Datenspeicherung .
Wenn wir mit einem Objekt arbeiten, das zu einem anderen Code „gehört“, vielleicht sogar zu einer Bibliothek eines Drittanbieters, und einige damit verbundene Daten speichern möchten, die nur existieren sollten, solange das Objekt aktiv ist, dann ist WeakMap
genau das Richtige benötigt.
Wir legen die Daten in einer WeakMap
ab und verwenden dabei das Objekt als Schlüssel. Wenn das Objekt durch Garbage Collection erfasst wird, verschwinden auch diese Daten automatisch.
schwachMap.set(john, "geheime Dokumente"); // wenn John stirbt, werden geheime Dokumente automatisch vernichtet
Schauen wir uns ein Beispiel an.
Wir haben zum Beispiel Code, der die Besuchszählung für Benutzer speichert. Die Informationen werden in einer Karte gespeichert: Ein Benutzerobjekt ist der Schlüssel und die Anzahl der Besuche ist der Wert. Wenn ein Benutzer die Seite verlässt (sein Objekt wird im Garbage Collection gesammelt), möchten wir die Anzahl seiner Besuche nicht mehr speichern.
Hier ist ein Beispiel für eine Zählfunktion mit Map
:
// ? visitsCount.js let visitsCountMap = new Map(); // map: user => Anzahl der Besuche // Erhöhen Sie die Anzahl der Besuche Funktion countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); }
Und hier ist ein weiterer Teil des Codes, möglicherweise eine andere Datei, die ihn verwendet:
// ? main.js let john = { name: "John" }; countUser(john); // seine Besuche zählen // Später verlässt John uns john = null;
Jetzt sollte john
Objekt durch Garbage Collection erfasst werden, verbleibt aber im Speicher, da es ein Schlüssel in visitsCountMap
ist.
Wir müssen visitsCountMap
bereinigen, wenn wir Benutzer entfernen, sonst wächst der Speicher auf unbestimmte Zeit. In komplexen Architekturen kann eine solche Reinigung zu einer mühsamen Aufgabe werden.
Wir können dies vermeiden, indem wir stattdessen zu WeakMap
wechseln:
// ? visitsCount.js let visitsCountMap = new WeakMap(); // schwache Karte: Benutzer => Anzahl der Besuche // Erhöhen Sie die Anzahl der Besuche Funktion countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); }
Jetzt müssen wir visitsCountMap
nicht mehr bereinigen. Nachdem john
Objekt außer als Schlüssel von WeakMap
nicht mehr erreichbar ist, wird es zusammen mit den Informationen dieses Schlüssels von WeakMap
aus dem Speicher entfernt.
Ein weiteres häufiges Beispiel ist das Caching. Wir können Ergebnisse einer Funktion speichern („cachen“), sodass zukünftige Aufrufe desselben Objekts es wiederverwenden können.
Um dies zu erreichen, können wir Map
verwenden (kein optimales Szenario):
// ? Cache.js let cache = new Map(); // Berechnen und merken Sie sich das Ergebnis Funktionsprozess(obj) { if (!cache.has(obj)) { let result = /* Berechnungen des Ergebnisses für */ obj; cache.set(obj, result); Ergebnis zurückgeben; } return Cache.get(obj); } // Jetzt verwenden wir Process() in einer anderen Datei: // ? main.js let obj = {/* Nehmen wir an, wir haben ein Objekt */}; let result1 = Process(obj); // berechnet // ...später, von einer anderen Stelle des Codes... let result2 = Process(obj); // gespeichertes Ergebnis aus dem Cache // ...später, wenn das Objekt nicht mehr benötigt wird: obj = null; alarm(cache.size); // 1 (Autsch! Das Objekt befindet sich immer noch im Cache und beansprucht Speicher!)
Bei mehreren Aufrufen von process(obj)
mit demselben Objekt wird das Ergebnis nur beim ersten Mal berechnet und dann einfach aus cache
entnommen. Der Nachteil besteht darin, dass wir cache
bereinigen müssen, wenn das Objekt nicht mehr benötigt wird.
Wenn wir Map
durch WeakMap
ersetzen, verschwindet dieses Problem. Das zwischengespeicherte Ergebnis wird automatisch aus dem Speicher entfernt, nachdem das Objekt durch Müll gesammelt wurde.
// ? Cache.js let cache = new WeakMap(); // Berechnen und merken Sie sich das Ergebnis Funktionsprozess(obj) { if (!cache.has(obj)) { let result = /* berechnet das Ergebnis für */ obj; cache.set(obj, result); Ergebnis zurückgeben; } return Cache.get(obj); } // ? main.js let obj = {/* irgendein Objekt */}; let result1 = Process(obj); let result2 = Process(obj); // ...später, wenn das Objekt nicht mehr benötigt wird: obj = null; // Cache.size kann nicht abgerufen werden, da es sich um eine WeakMap handelt. // aber es ist 0 oder wird bald 0 sein // Wenn obj Müll gesammelt wird, werden auch zwischengespeicherte Daten entfernt
WeakSet
verhält sich ähnlich:
Es ist analog zu Set
, aber wir können nur Objekte zu WeakSet
hinzufügen (keine Grundelemente).
Ein Objekt existiert in der Menge, solange es von einem anderen Ort aus erreichbar ist.
Wie Set
unterstützt es add
, has
und delete
, jedoch nicht size
, keys()
und keine Iterationen.
Da es „schwach“ ist, dient es auch als zusätzlicher Speicher. Aber nicht für willkürliche Daten, sondern für „Ja/Nein“-Fakten. Eine Mitgliedschaft in WeakSet
kann etwas über das Objekt bedeuten.
Beispielsweise können wir Benutzer zu WeakSet
hinzufügen, um den Überblick über diejenigen zu behalten, die unsere Website besucht haben:
let besuchteSet = new WeakSet(); let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; besuchteSet.add(john); // John hat uns besucht besuchteSet.add(pete); // Dann Pete besuchteSet.add(john); // Wieder John // besuchteSet hat jetzt 2 Benutzer // Überprüfen Sie, ob John zu Besuch war? alarm(visitedSet.has(john)); // WAHR // Überprüfen Sie, ob Mary zu Besuch war? alarm(visitedSet.has(mary)); // FALSCH john = null; // besuchteSet wird automatisch bereinigt
Die bemerkenswerteste Einschränkung von WeakMap
und WeakSet
ist das Fehlen von Iterationen und die Unfähigkeit, alle aktuellen Inhalte abzurufen. Das mag unbequem erscheinen, hindert WeakMap/WeakSet
aber nicht daran, ihre Hauptaufgabe zu erfüllen – eine „zusätzliche“ Speicherung von Daten für Objekte zu sein, die an einem anderen Ort gespeichert/verwaltet werden.
WeakMap
ist Map
-ähnliche Sammlung, die nur Objekte als Schlüssel zulässt und diese zusammen mit dem zugehörigen Wert entfernt, sobald auf sie auf andere Weise nicht mehr zugegriffen werden kann.
WeakSet
ist Set
-ähnliche Sammlung, die nur Objekte speichert und diese entfernt, sobald auf andere Weise kein Zugriff mehr auf sie möglich ist.
Ihre Hauptvorteile bestehen darin, dass sie einen schwachen Bezug zu Objekten haben und daher vom Garbage Collector leicht entfernt werden können.
Dies geht jedoch mit der fehlenden Unterstützung für clear
, size
, keys
, values
einher.
WeakMap
und WeakSet
werden zusätzlich zum „primären“ Objektspeicher als „sekundäre“ Datenstrukturen verwendet. Sobald das Objekt aus dem Primärspeicher entfernt wird und es nur als Schlüssel von WeakMap
oder in einem WeakSet
gefunden wird, wird es automatisch bereinigt.
Wichtigkeit: 5
Es gibt eine Reihe von Nachrichten:
let message = [ {text: „Hallo“, von: „John“}, {text: „Wie geht es?“, aus: „John“}, {text: „Bis bald“, von: „Alice“} ];
Ihr Code kann darauf zugreifen, aber die Nachrichten werden vom Code einer anderen Person verwaltet. Durch diesen Code werden regelmäßig neue Nachrichten hinzugefügt, alte entfernt, und Sie wissen nicht genau, zu welchem Zeitpunkt dies geschieht.
Welche Datenstruktur könnten Sie nun verwenden, um Informationen darüber zu speichern, ob die Nachricht „gelesen“ wurde? Die Struktur muss gut geeignet sein, um die Antwort „Wurde es gelesen?“ zu geben. für das angegebene Nachrichtenobjekt.
PS: Wenn eine Nachricht aus messages
entfernt wird, sollte sie auch aus Ihrer Struktur verschwinden.
PPS Wir sollten Nachrichtenobjekte nicht ändern, sondern ihnen unsere Eigenschaften hinzufügen. Da sie durch den Code einer anderen Person verwaltet werden, kann dies schlimme Folgen haben.
Speichern wir gelesene Nachrichten in WeakSet
:
let message = [ {text: „Hallo“, von: „John“}, {text: „Wie geht es?“, aus: „John“}, {text: „Bis bald“, von: „Alice“} ]; let readMessages = new WeakSet(); // zwei Nachrichten wurden gelesen readMessages.add(messages[0]); readMessages.add(messages[1]); // readMessages hat 2 Elemente // ...lasst uns die erste Nachricht noch einmal lesen! readMessages.add(messages[0]); // readMessages hat immer noch 2 eindeutige Elemente // Antwort: Wurde die Nachricht[0] gelesen? alarm("Nachricht 0 lesen: " + readMessages.has(messages[0])); // WAHR Nachrichten.shift(); // jetzt hat readMessages 1 Element (technisch gesehen kann der Speicher später bereinigt werden)
Mit dem WeakSet
können Sie eine Reihe von Nachrichten speichern und auf einfache Weise prüfen, ob darin eine Nachricht vorhanden ist.
Es reinigt sich automatisch. Der Nachteil besteht darin, dass wir nicht darüber iterieren können und nicht direkt „alle gelesenen Nachrichten“ daraus abrufen können. Wir können dies jedoch erreichen, indem wir alle Nachrichten durchlaufen und die im Satz enthaltenen Nachrichten filtern.
Eine andere, andere Lösung könnte darin bestehen, einer Nachricht nach dem Lesen eine Eigenschaft wie message.isRead=true
hinzuzufügen. Da Nachrichtenobjekte von einem anderen Code verwaltet werden, wird davon im Allgemeinen abgeraten, aber wir können eine symbolische Eigenschaft verwenden, um Konflikte zu vermeiden.
So was:
// Die symbolische Eigenschaft ist nur unserem Code bekannt let isRead = Symbol("isRead"); Nachrichten[0][isRead] = true;
Jetzt wird der Code von Drittanbietern unsere zusätzliche Eigenschaft wahrscheinlich nicht sehen.
Obwohl Symbole die Wahrscheinlichkeit von Problemen verringern können, ist die Verwendung von WeakSet
aus architektonischer Sicht besser.
Wichtigkeit: 5
Es gibt eine Reihe von Nachrichten wie in der vorherigen Aufgabe. Die Situation ist ähnlich.
let message = [ {text: „Hallo“, von: „John“}, {text: „Wie geht es?“, aus: „John“}, {text: „Bis bald“, von: „Alice“} ];
Die Frage ist nun: Welche Datenstruktur würden Sie zum Speichern der Informationen vorschlagen: „Wann wurde die Nachricht gelesen?“
In der vorherigen Aufgabe mussten wir nur die Tatsache „Ja/Nein“ speichern. Jetzt müssen wir das Datum speichern und es sollte nur so lange im Speicher bleiben, bis die Nachricht durch den Müll gesammelt wird.
PS-Daten können als Objekte der integrierten Date
-Klasse gespeichert werden, auf die wir später noch eingehen werden.
Um ein Datum zu speichern, können wir WeakMap
verwenden:
let message = [ {text: „Hallo“, von: „John“}, {text: „Wie geht es?“, aus: „John“}, {text: „Bis bald“, von: „Alice“} ]; let readMap = new WeakMap(); readMap.set(messages[0], neues Datum(2017, 1, 1)); // Datumsobjekt, das wir später untersuchen werden