Articles

WeakMaps (`WeakMap`) (fortgeschritten) – JavaScript für ungeduldige Programmierer (ES2021 edition)

(Anzeige, bitte nicht blockieren.)

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.

Weiter: 35 Sätze (Set)