WeakMaps (`WeakMap`) (fortgeschritten) – JavaScript für ungeduldige Programmierer (ES2021 edition)
34 WeakMaps (WeakMap) (fortgeschritten)
- 34.1 WeakMaps sind Blackboxen
- 34.2 Die Schlüssel einer WeakMap sind schwach gehalten
- 34.2.1 Alle WeakMap-Schlüssel müssen Objekte sein
- 34.2.2 Anwendungsfall: Anhängen von Werten an Objekte
- 34.3 Beispiele
- 34.3.1 Zwischenspeichern von berechneten Ergebnissen über WeakMaps
- 34.3.2 Aufbewahren von privaten Daten in WeakMaps
- 34.4 WeakMap-API
WeakMaps sind ähnlich wie Maps, mit den folgenden Unterschieden:
- Sie sind Black Boxes, bei denen auf einen Wert nur zugegriffen werden kann, wenn man sowohl die WeakMap als auch den Schlüssel besitzt.
- Die Schlüssel einer WeakMap sind schwach gehalten: wenn ein Objekt ein Schlüssel in einer WeakMap ist, kann es immer noch im Müll landen. Das erlaubt es uns, WeakMaps zu benutzen, um Daten an Objekte anzuhängen.
Die nächsten beiden Abschnitte untersuchen genauer, was das bedeutet.
34.1 WeakMaps sind Black Boxes
Es ist unmöglich zu untersuchen, was sich in einer WeakMap befindet:
- Zum Beispiel kann man nicht über Schlüssel, Werte oder Einträge iterieren oder eine Schleife machen. Und man kann die Größe nicht berechnen.
- Außerdem kann man eine WeakMap auch nicht löschen – man muss eine neue Instanz erstellen.
Diese Einschränkungen ermöglichen eine Sicherheitseigenschaft. Zitat Mark Miller:
Das Mapping von weakmap/key pair value kann nur von jemandem beobachtet oder beeinflusst werden, der sowohl die weakmap als auch den Schlüssel besitzt. Mit
clear()
hätte jemand, der nur die WeakMap hat, die Zuordnung von WeakMap und Schlüssel zu Wert beeinflussen können.
34.2 Die Schlüssel einer WeakMap sind schwach gehalten
Die Schlüssel einer WeakMap werden als schwach gehalten bezeichnet: Normalerweise, wenn ein Objekt auf ein anderes verweist, dann kann das letztere Objekt nicht weggeworfen werden, solange das erstere existiert. Bei einer WeakMap ist das anders: Wenn ein Objekt ein Schlüssel ist und nicht auf ein anderes verweist, kann es garbage-collected werden, solange die WeakMap noch existiert. Das führt auch dazu, dass der entsprechende Eintrag entfernt wird (aber es gibt keine Möglichkeit, das zu beobachten).
34.2.1 All WeakMap keys must be objects
All WeakMap keys must be objects. Man bekommt einen Fehler, wenn man einen primitiven Wert verwendet:
> const wm = new WeakMap();> wm.set(123, 'test')TypeError: Invalid value used as weak map key
Mit primitiven Werten als Schlüssel wären WeakMaps keine Blackboxen mehr. Aber da primitive Werte nie im Müll landen, profitiert man sowieso nicht von schwach gehaltenen Schlüsseln und kann genauso gut eine normale Map verwenden.
34.2.2 Anwendungsfall: Werte an Objekte anhängen
Dies ist der Hauptanwendungsfall für WeakMaps: man kann sie benutzen, um Werte extern an Objekte anzuhängen – zum Beispiel:
const wm = new WeakMap();{ const obj = {}; wm.set(obj, 'attachedValue'); // (A)}// (B)
In Zeile A hängen wir einen Wert an obj
. In Zeile B kann obj
bereits als Müll gesammelt werden, obwohl wm
noch existiert. Diese Technik des Anhängens eines Wertes an ein Objekt ist gleichbedeutend mit einer Eigenschaft dieses Objekts, die extern gespeichert wird. Wäre wm
eine Eigenschaft, würde der vorherige Code wie folgt aussehen:
{ const obj = {}; obj.wm = 'attachedValue';}
34.3 Beispiele
34.3.1 Zwischenspeichern von berechneten Ergebnissen über WeakMaps
Mit WeakMaps können Sie zuvor berechnete Ergebnisse mit Objekten verknüpfen, ohne sich um die Speicherverwaltung kümmern zu müssen. Die folgende Funktion countOwnKeys()
ist ein Beispiel: Sie speichert frühere Ergebnisse in der WeakMap cache
.
const cache = new WeakMap();function countOwnKeys(obj) { if (cache.has(obj)) { return ; } else { const count = Object.keys(obj).length; cache.set(obj, count); return ; }}
Wenn wir diese Funktion mit einem Objekt obj
verwenden, können Sie sehen, dass das Ergebnis nur für den ersten Aufruf berechnet wird, während für den zweiten Aufruf ein zwischengespeicherter Wert verwendet wird:
> const obj = { foo: 1, bar: 2};> countOwnKeys(obj)> countOwnKeys(obj)
34.3.2 Private Daten in WeakMaps
Im folgenden Code werden die WeakMaps _counter
und _action
verwendet, um die Werte von virtuellen Eigenschaften von Instanzen von Countdown
zu speichern:
const _counter = new WeakMap();const _action = new WeakMap();class Countdown { constructor(counter, action) { _counter.set(this, counter); _action.set(this, action); } dec() { let counter = _counter.get(this); counter--; _counter.set(this, counter); if (counter === 0) { _action.get(this)(); } }}// The two pseudo-properties are truly private:assert.deepEqual( Object.keys(new Countdown()), );
So wird Countdown
verwendet:
let invoked = false;const cd = new Countdown(3, () => invoked = true);cd.dec(); assert.equal(invoked, false);cd.dec(); assert.equal(invoked, false);cd.dec(); assert.equal(invoked, true);
Übung: WeakMaps für private Daten
exercises/weakmaps/weakmaps_private_data_test.mjs
34.4 WeakMap API
Der Konstruktor und die vier Methoden von WeakMap
funktionieren genauso wie ihre Map
Entsprechungen:
-
new WeakMap<K, V>(entries?: Iterable<>)
-
.delete(key: K) : boolean
-
.get(key: K) : V
-
.has(key: K) : boolean
-
.set(key: K, value: V) : this
Quiz
Siehe Quiz-App.
Set
)