JavaScript für ungeduldige Programmierer (ES2022-Ausgabe)
Bitte unterstützen Sie dieses Buch: kaufen Sie es oder spenden Sie
(Werbung, bitte nicht blockieren.)

34 WeakMaps (WeakMap) (fortgeschritten)



WeakMaps sind ähnlich wie Maps, mit den folgenden Unterschieden

Die nächsten beiden Abschnitte untersuchen detaillierter, was das bedeutet.

34.1 WeakMaps sind Black Boxes

Es ist unmöglich, den Inhalt einer WeakMap zu inspizieren.

Diese Einschränkungen ermöglichen eine Sicherheitseigenschaft. Zitiert von Mark Miller

Die Zuordnung von WeakMap/Schlüssel-Paar-Wert 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 besitzt, die Zuordnung von WeakMap und Schlüssel zu Wert beeinflussen können.

34.2 Die Schlüssel einer WeakMap werden schwach gehalten

Die Schlüssel einer WeakMap werden als schwach gehalten bezeichnet: Normalerweise kann ein Objekt nicht vom Garbage Collector eingesammelt werden, solange ein anderes Objekt darauf verweist. Mit einer WeakMap ist das anders: Wenn ein Objekt ein Schlüssel ist und nicht mehr anderweitig referenziert wird, kann es vom Garbage Collector eingesammelt werden, auch wenn die WeakMap noch existiert. Das führt auch dazu, dass der entsprechende Eintrag entfernt wird (aber es gibt keine Möglichkeit, dies zu beobachten).

34.2.1 Alle Schlüssel von WeakMap müssen Objekte sein

Alle Schlüssel von WeakMap müssen Objekte sein. Man erhält 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üsseln wären WeakMaps keine Black Boxes mehr. Aber da primitive Werte niemals vom Garbage Collector eingesammelt werden, profitiert man ohnehin nicht von schwach gehaltenen Schlüsseln und kann genauso gut eine normale Map verwenden.

34.2.2 Anwendungsfall: Werte an Objekte anhängen

Das ist der Hauptanwendungsfall für WeakMaps: Man kann sie verwenden, um externe Werte 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 an. In Zeile B kann obj bereits vom Garbage Collector eingesammelt werden, obwohl wm noch existiert. Diese Technik, einen Wert an ein Objekt anzuhängen, ist äquivalent dazu, dass eine Eigenschaft dieses Objekts extern gespeichert wird. Wenn wm eine Eigenschaft wäre, würde der vorherige Code wie folgt aussehen

{
  const obj = {};
  obj.wm = 'attachedValue';
}

34.3 Beispiele

34.3.1 Zwischenspeichern berechneter Ergebnisse über WeakMaps

Mit WeakMaps kann man 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 im WeakMap cache zwischen.

const cache = new WeakMap();
function countOwnKeys(obj) {
  if (cache.has(obj)) {
    return [cache.get(obj), 'cached'];
  } else {
    const count = Object.keys(obj).length;
    cache.set(obj, count);
    return [count, 'computed'];
  }
}

Wenn wir diese Funktion mit einem Objekt obj verwenden, sieht man, dass das Ergebnis nur beim ersten Aufruf berechnet wird, während beim zweiten Aufruf ein zwischengespeicherter Wert verwendet wird.

> const obj = { foo: 1, bar: 2};
> countOwnKeys(obj)
[2, 'computed']
> countOwnKeys(obj)
[2, 'cached']

34.3.2 Private Daten in WeakMaps halten

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-Äquivalente.

  Quiz

Siehe Quiz-App.