Articles

WeakMaps (`WeakMap`) (avancerad) – JavaScript för otåliga programmerare (ES2021 edition)

(Annons, blockera inte.)

34 WeakMaps (WeakMap) (avancerad)

  • 34.1 WeakMaps är svarta lådor
  • 34.2 Nycklarna i en WeakMap är svagt hållna
    • 34.2.1 Alla WeakMap-nycklar måste vara objekt
    • 34.2.2 Användningsfall: bifoga värden till objekt
  • 34.3 Exempel
  • 34.3.1 Cachelagring av beräknade resultat via WeakMaps
  • 34.3.2 Behållning av privata data i WeakMaps
  • 34.4 WeakMap API
  • WeakMaps liknar Maps, med följande skillnader:

    • De är svarta lådor, där ett värde endast kan nås om du har både WeakMap och nyckeln.
    • Nycklarna i en WeakMap är svagt hållna: om ett objekt är en nyckel i en WeakMap kan det fortfarande skräpkasseras. Det gör att vi kan använda WeakMaps för att koppla data till objekt.

    De två följande avsnitten undersöker närmare vad det innebär.

    34.1 WeakMaps är svarta lådor

    Det är omöjligt att inspektera vad som finns inuti en WeakMap:

    • Det går till exempel inte att iterera eller slinga över nycklar, värden eller poster. Och du kan inte beräkna storleken.
    • Det går inte heller att rensa en WeakMap – du måste skapa en ny instans.

    Dessa begränsningar aktiverar en säkerhetsegenskap. Jag citerar Mark Miller:

    Mappningen från weakmap/nyckelparets värde kan endast observeras eller påverkas av någon som har både weakmap och nyckel. Med clear() skulle någon som endast har WeakMap ha kunnat påverka mappningen WeakMap-och-nyckel-till-värde.

    34.2 Nycklarna i en WeakMap är svagt hållna

    Nycklarna i en WeakMap sägs vara svagt hållna: Om ett objekt hänvisar till ett annat kan det sistnämnda objektet normalt sett inte samlas in så länge det förstnämnda objektet existerar. Med en WeakMap är det annorlunda: Om ett objekt är en nyckel och inte hänvisas till någon annanstans kan det samlas in i sopor så länge WeakMap fortfarande existerar. Det leder också till att motsvarande post tas bort (men det finns inget sätt att observera det).

    34.2.1 Alla WeakMap-nycklar måste vara objekt

    Alla WeakMap-nycklar måste vara objekt. Du får ett fel om du använder ett primitivt värde:

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

    Med primitiva värden som nycklar skulle WeakMaps inte längre vara svarta lådor. Men med tanke på att primitiva värden aldrig samlas in som skräp, tjänar du ändå inte på svagt hållna nycklar och kan lika gärna använda en normal Map.

    34.2.2 Användningsfall: att knyta värden till objekt

    Det här är det huvudsakliga användningsfallet för WeakMaps: du kan använda dem för att externt knyta värden till objekt – till exempel:

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

    I rad A knyter vi ett värde till obj. I rad B kan obj redan vara skräpkollekterad, även om wm fortfarande existerar. Denna teknik att knyta ett värde till ett objekt är likvärdig med att en egenskap hos det objektet lagras externt. Om wm var en egenskap skulle den tidigare koden se ut på följande sätt:

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

    34.3 Exempel

    34.3.1 Cachelagring av beräknade resultat via WeakMaps

    Med WeakMaps kan du associera tidigare beräknade resultat med objekt utan att behöva oroa dig för minneshantering. Följande funktion countOwnKeys() är ett exempel: den lagrar tidigare resultat i 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 ; }}

    Om vi använder den här funktionen med ett objekt obj kan du se att resultatet endast beräknas för det första anropet, medan ett lagrat värde används för det andra anropet:

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

    34.3.2 Förvara privata data i WeakMaps

    I följande kod används WeakMaps _counter och _action för att lagra värdena för virtuella egenskaper hos instanser av Countdown:

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

    Så här används Countdown:

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

    Övning: WeakMaps för privata data

    exercises/weakmaps/weakmaps_private_data_test.mjs

    34.4 WeakMap API

    Konstruktören och de fyra metoderna i WeakMap fungerar på samma sätt som deras motsvarigheter i Map:

    • new WeakMap<K, V>(entries?: Iterable<>)
    • .delete(key: K) : boolean
    • .get(key: K) : V
    • .has(key: K) : boolean
    • .set(key: K, value: V) : this

    Frågesport

    Se frågesportsapplikationen.

    Nästa: 35 uppsättningar (Set)