Articles

WeakMaps (`WeakMap`) (gevorderd) – JavaScript voor ongeduldige programmeurs (ES2021 editie)

(Advertentie, niet blokkeren a.u.b.)

34 WeakMaps (WeakMap) (gevorderd)

  • 34.1 WeakMaps zijn zwarte dozen
  • 34.2 De sleutels van een WeakMap worden zwak vastgehouden
    • 34.2.1 Alle WeakMap-sleutels moeten objecten zijn
    • 34.2.2 Use case: waarden aan objecten koppelen
  • 34.3 Voorbeelden
    • 34.3.1 Cachen van berekende resultaten via WeakMaps
    • 34.3.2 Bewaren van privégegevens in WeakMaps
  • 34.4 WeakMap API

WeakMaps zijn vergelijkbaar met Maps, met de volgende verschillen:

  • Ze zijn zwarte dozen, waarbij een waarde alleen toegankelijk is als je zowel de WeakMap als de sleutel hebt.
  • De sleutels van een WeakMap worden zwak vastgehouden: als een object een sleutel is in een WeakMap, kan het nog steeds in de vuilnisbak worden gegooid. Dat stelt ons in staat WeakMaps te gebruiken om gegevens aan objecten te koppelen.

De volgende twee secties gaan meer in detail in op wat dat betekent.

34.1 WeakMaps zijn zwarte dozen

Het is onmogelijk om te inspecteren wat er in een WeakMap zit:

  • Zo kun je bijvoorbeeld niet itereren of lussen over sleutels, waarden of entries. En je kunt de grootte niet berekenen.
  • Daarnaast kun je een WeakMap ook niet wissen – je moet een nieuwe instantie maken.

Deze beperkingen maken een beveiligingseigenschap mogelijk. Citaat van Mark Miller:

De toewijzing van de waarde van een weakmap/sleutelpaar kan alleen worden waargenomen of beïnvloed door iemand die zowel de weakmap als de sleutel heeft. Met clear() had iemand met alleen de WeakMap de toewijzing van de WeakMap en de sleutel aan de waarde kunnen beïnvloeden.

34.2 De sleutels van een WeakMap worden zwak gehouden

Van de sleutels van een WeakMap wordt gezegd dat ze zwak worden gehouden: Normaal gesproken als een object naar een ander object verwijst, dan kan het laatste object niet worden vuilgemaakt zolang het eerste bestaat. Bij een WeakMap is dat anders: Als een object een sleutel is en niet ergens anders naar verwijst, kan het worden afgekeurd zolang de WeakMap nog bestaat. Dat leidt er ook toe dat de bijbehorende entry wordt verwijderd (maar er is geen manier om dat waar te nemen).

34.2.1 Alle WeakMap sleutels moeten objecten zijn

Alle WeakMap sleutels moeten objecten zijn. U krijgt een foutmelding als u een primitieve waarde gebruikt:

> const wm = new WeakMap();> wm.set(123, 'test')TypeError: Invalid value used as weak map key

Met primitieve waarden als sleutel zouden WeakMaps geen zwarte dozen meer zijn. Maar aangezien primitieve waarden nooit vuilnis worden verzameld, heb je sowieso geen baat bij zwak gehouden sleutels, en kun je net zo goed een normale Map gebruiken.

34.2.2 Use case: waarden aan objecten koppelen

Dit is de belangrijkste use case voor WeakMaps: je kunt ze gebruiken om extern waarden aan objecten te koppelen – bijvoorbeeld:

const wm = new WeakMap();{ const obj = {}; wm.set(obj, 'attachedValue'); // (A)}// (B)

In regel A koppelen we een waarde aan obj. In regel B, kan obj al worden vuilnis opgehaald, ook al bestaat wm nog steeds. Deze techniek van het hechten van een waarde aan een object is gelijk aan een eigenschap van dat object die extern wordt opgeslagen. Indien wm een eigenschap zou zijn, zou de voorgaande code er als volgt uitzien:

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

34.3 Voorbeelden

34.3.1 Caching van berekende resultaten via WeakMaps

Met WeakMaps kunt u eerder berekende resultaten aan objecten koppelen zonder dat u zich zorgen hoeft te maken over geheugenbeheer. De volgende functie countOwnKeys() is een voorbeeld: deze slaat eerder berekende resultaten op in de 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 ; }}

Als we deze functie gebruiken met een object obj, kunt u zien dat het resultaat alleen wordt berekend voor de eerste aanroep, terwijl een in de cache opgeslagen waarde wordt gebruikt voor de tweede aanroep:

> const obj = { foo: 1, bar: 2};> countOwnKeys(obj)> countOwnKeys(obj)

34.3.2 Privégegevens bewaren in WeakMaps

In de volgende code worden de WeakMaps _counter en _action gebruikt om de waarden van virtuele eigenschappen van instanties van Countdown op te slaan:

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()), );

Zo wordt Countdown gebruikt:

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);

Oefening: WeakMaps voor privégegevens

exercises/weakmaps/weakmaps_private_data_test.mjs

34.4 WeakMap API

De constructor en de vier methoden van WeakMap werken hetzelfde als hun Map equivalenten:

  • 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

Zie quiz-app.

Volgende: 35 Sets (Set)