WeakMaps (`WeakMap`) (gevorderd) – JavaScript voor ongeduldige programmeurs (ES2021 editie)
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.
Set
)