JavaScript WeakMap und WeakSet
In diesem Kapitel erhalten Sie umfassende Informationen über nützliche Methoden zur Datenspeicherung in JavaScript. Lernen Sie WeakMap und WeakSet kennen.
Einführung in JavaScript WeakMap und WeakSet
WeakMap und WeakSet sind spezialisierte Varianten von Map und Set. Sie sehen in der Verwendung fast identisch aus, unterscheiden sich jedoch in einem entscheidenden Punkt: Sie halten ihre Schlüssel (bzw. Mitglieder) schwach. Eine schwache Referenz hindert die JavaScript-Engine nicht daran, ein object aus dem Speicher zu entfernen. Wenn das Einzige, das auf ein object zeigt, ein WeakMap-Schlüssel oder ein WeakSet-Mitglied ist, kann das object trotzdem durch die Garbage Collection entfernt werden.
Diese eine Eigenschaft verleiht WeakMap und WeakSet ihren Zweck: Sie ermöglichen es, einem object zusätzliche Daten zuzuordnen, ohne die Lebensdauer dieses objects zu kontrollieren. Wenn das object verschwindet, verschwinden die zugehörigen Daten automatisch mit — ganz ohne Bereinigungscode.
Dieses Kapitel behandelt die Regeln, die sie von regulären Strukturen unterscheiden, die damit verbundenen Kompromisse und die realen Situationen, in denen sie das richtige Werkzeug sind.
Was „schwach" bedeutet
In einer regulären Map ist ein Schlüssel eine starke Referenz. Solange die Map existiert, werden alle Schlüssel, die sie enthält, am Leben erhalten — selbst wenn kein anderer Teil Ihres Programms diesen Schlüssel noch verwendet. Das ist eine häufige Quelle von Speicherlecks: Sie vergessen, einen Eintrag zu deleten, und das Schlüssel-object bleibt für immer bestehen.
Ein WeakMap dreht dies um. Der Schlüssel wird schwach referenziert, sodass die Engine das Schlüssel-object zurückfordern kann, sobald nichts anderes mehr darauf verweist. Wenn das passiert, verschwindet der Eintrag einfach aus der WeakMap.
let visits = new WeakMap();
let user = { name: "Alice" };
visits.set(user, 10);
console.log(visits.get(user)); // 10
console.log(visits.has(user)); // true
user = null; // the object is now unreachable elsewhere
// The WeakMap entry becomes eligible for garbage collection.
// You cannot observe exactly when it is removed.WeakMap
Eine WeakMap ist eine Sammlung von Schlüssel-Wert-Paaren, bei der die Schlüssel objects sein müssen und die Werte beliebig sein dürfen.
Schlüssel müssen objects sein
Primitive Werte (strings, numbers, booleans, symbol, null, undefined) können keine Schlüssel sein. Der Grund ergibt sich aus dem Design: Primitive werden nicht auf die gleiche Weise wie objects durch Garbage Collection bereinigt, daher ist das „schwache Halten" von ihnen bedeutungslos. Der Versuch, einen primitiven Wert zu verwenden, wirft einen TypeError.
let wm = new WeakMap();
wm.set("a string", 1); // TypeError: Invalid value used as weak map keyVerfügbare Methoden
Eine WeakMap unterstützt nur vier Operationen:
set(key, value)— speichert einen Wert unter einem object-Schlüssel.get(key)— liest den Wert oder gibtundefinedzurück, falls nicht vorhanden.has(key)— prüft, ob ein Schlüssel existiert.delete(key)— entfernt einen Eintrag.
let wm = new WeakMap();
let key = { id: 1 };
wm.set(key, "data");
console.log(wm.has(key)); // true
console.log(wm.get(key)); // "data"
wm.delete(key);
console.log(wm.has(key)); // falseKeine Iteration und keine Größe
Es gibt keine size-Eigenschaft, kein clear() und keine Möglichkeit, über eine WeakMap zu iterieren (kein keys(), values(), entries() oder forEach). Das ist kein Versehen. Da Einträge zu jedem Zeitpunkt verschwinden können, wenn der Garbage Collector läuft, sind die Inhalte nicht-deterministisch — sie offenzulegen würde es Ihrem Code ermöglichen, das GC-Timing zu beobachten, was die Sprache bewusst verbirgt.
let wm = new WeakMap();
console.log("size" in wm); // false
console.log(wm[Symbol.iterator]); // undefinedWenn Sie Einträge zählen, iterieren oder primitive Schlüssel verwenden müssen, verwenden Sie stattdessen eine reguläre Map.
WeakSet
Ein WeakSet ist das Set-Äquivalent: eine Sammlung von eindeutigen objects, die schwach gehalten werden. Wie WeakMap müssen seine Mitglieder objects sein, und es bietet keine Iteration oder size.
let visited = new WeakSet();
let a = { id: 1 };
let b = { id: 2 };
visited.add(a);
console.log(visited.has(a)); // true
console.log(visited.has(b)); // false
visited.delete(a);
console.log(visited.has(a)); // falseDie vollständige API besteht nur aus add(value), has(value) und delete(value).
Praktische Anwendungsfälle
Caching und Memoization
Speichern Sie das Ergebnis einer aufwendigen Berechnung mit dem zugehörigen object als Schlüssel im Cache. Da der Schlüssel schwach ist, hält der Cache ein nicht mehr benötigtes object niemals am Leben.
const cache = new WeakMap();
function process(obj) {
if (cache.has(obj)) {
return cache.get(obj); // reuse the cached result
}
const result = obj.value * 2; // pretend this is expensive
cache.set(obj, result);
return result;
}
let data = { value: 21 };
console.log(process(data)); // 42 (computed)
console.log(process(data)); // 42 (from cache)Private objektbezogene Daten
Speichern Sie Daten, die zu einem object gehören, ohne eine Eigenschaft zum object selbst hinzuzufügen (die jeder lesen oder überschreiben könnte). Dies ist ein klassischer Weg, um wirklich private Felder zu emulieren, ähnlich wie Methoden über this an objects gebunden werden.
const balances = new WeakMap();
class Account {
constructor(amount) {
balances.set(this, amount); // private to this module
}
deposit(n) {
balances.set(this, balances.get(this) + n);
}
get balance() {
return balances.get(this);
}
}
const acc = new Account(100);
acc.deposit(50);
console.log(acc.balance); // 150
// There is no `amount` property on `acc` itself to tamper with.Wenn die Account-Instanz nicht mehr referenziert wird, wird ihr Kontostand-Eintrag automatisch bereinigt.
Metadaten für DOM-Knoten
Ein häufiges Speicherleck auf lang laufenden Seiten entsteht, wenn eine Map mit DOM-Knoten-Metadaten erhalten bleibt, nachdem die Knoten aus dem Dokument entfernt wurden. Mit einem WeakSet/WeakMap werden die Metadaten eines Knotens ebenfalls zurückgefordert, sobald er getrennt und dereferenziert wird.
const seen = new WeakSet();
function markVisited(node) {
if (seen.has(node)) return false; // already processed
seen.add(node);
return true;
}
// When the node leaves the DOM and nothing else holds it,
// it disappears from `seen` without manual cleanup.WeakMap/WeakSet vs. Map/Set
| Fähigkeit | Map / Set | WeakMap / WeakSet |
|---|---|---|
| Schlüssel / Mitglieder | Beliebiger Wert | Nur objects |
| Referenzstärke | Stark (hält Schlüssel am Leben) | Schwach (erlaubt GC) |
size, clear() | Ja | Nein |
Iteration (forEach, keys…) | Ja | Nein |
| Am besten für | Allgemeine Sammlungen | Objektbezogene Daten mit automatischer Bereinigung |
Wählen Sie die schwache Variante nur, wenn Sie ausdrücklich möchten, dass die Engine die Bereinigung für Sie übernimmt, und Sie den Inhalt nicht aufzählen müssen. Für alles andere greifen Sie zur regulären Map und Set.