Articles

WeakMap (`WeakMap`) (avanzato) – JavaScript per programmatori impazienti (edizione ES2021)

(Ad, please don’t block.)

34 WeakMap (WeakMap) (avanzato)

  • 34.1 Le WeakMap sono scatole nere
  • 34.2 Le chiavi di una WeakMap sono tenute deboli
    • 34.2.1 Tutte le chiavi della WeakMap devono essere oggetti
    • 34.2.2 Caso d’uso: allegare valori agli oggetti
  • 34.3 Esempi
    • 34.3.1 Caching dei risultati calcolati tramite WeakMap
    • 34.3.2 Mantenere dati privati in WeakMap
  • 34.4 API WeakMap

Le WeakMap sono simili alle Mappe, con le seguenti differenze:

  • Sono scatole nere, dove si può accedere a un valore solo se si hanno sia la WeakMap che la chiave.
  • Le chiavi di una WeakMap sono tenute debolmente: se un oggetto è una chiave in una WeakMap, può ancora essere garbage-collected. Questo ci permette di usare le WeakMap per collegare i dati agli oggetti.

Le prossime due sezioni esaminano più in dettaglio cosa significa.

34.1 Le WeakMap sono scatole nere

È impossibile ispezionare cosa c’è dentro una WeakMap:

  • Per esempio, non è possibile iterare o fare un loop su chiavi, valori o voci. E non puoi calcolare la dimensione.
  • Inoltre, non puoi nemmeno cancellare una WeakMap – devi creare una nuova istanza.

Queste restrizioni abilitano una proprietà di sicurezza. Citando Mark Miller:

La mappatura dal valore della coppia weakmap/chiave può essere osservata o influenzata solo da qualcuno che ha sia la weakmap che la chiave. Con clear(), qualcuno con solo la WeakMap sarebbe stato in grado di influenzare la mappatura WeakMap-e-chiave-valore.

34.2 Le chiavi di una WeakMap sono tenute debolmente

Si dice che le chiavi di una WeakMap sono tenute debolmente: Normalmente, se un oggetto si riferisce ad un altro, allora quest’ultimo oggetto non può essere raccolto dalla spazzatura finché il primo esiste. Con una WeakMap, è diverso: se un oggetto è una chiave e non è riferito altrove, può essere garbage-collected mentre la WeakMap esiste ancora. Questo porta anche alla rimozione della voce corrispondente (ma non c’è modo di osservarlo).

34.2.1 Tutte le chiavi WeakMap devono essere oggetti

Tutte le chiavi WeakMap devono essere oggetti. Si ottiene un errore se si usa un valore primitivo:

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

Con valori primitivi come chiavi, le WeakMap non sarebbero più scatole nere. Ma dato che i valori primitivi non sono mai raccolti dalla spazzatura, non si trae profitto dalle chiavi tenute deboli comunque, e si può benissimo usare una normale Mappa.

34.2.2 Caso d’uso: attaccare valori agli oggetti

Questo è il principale caso d’uso delle WeakMaps: si possono usare per attaccare esternamente valori agli oggetti – per esempio:

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

Nella linea A, attacchiamo un valore a obj. Nella linea B, obj può già essere garbage-collected, anche se wm esiste ancora. Questa tecnica di attaccare un valore a un oggetto è equivalente a una proprietà di quell’oggetto memorizzata esternamente. Se wm fosse una proprietà, il codice precedente sarebbe il seguente:

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

34.3 Esempi

34.3.1 Caching dei risultati calcolati tramite WeakMaps

Con WeakMaps, è possibile associare risultati calcolati in precedenza agli oggetti senza doversi preoccupare della gestione della memoria. La seguente funzione countOwnKeys() è un esempio: essa memorizza i risultati precedenti nella 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 ; }}

Se usiamo questa funzione con un oggetto obj, potete vedere che il risultato viene calcolato solo per la prima invocazione, mentre un valore memorizzato nella cache viene usato per la seconda invocazione:

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

34.3.2 Mantenere i dati privati nelle WeakMaps

Nel seguente codice, le WeakMaps _counter e _action sono usate per memorizzare i valori delle proprietà virtuali delle istanze di 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()), );

Ecco come viene usato 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);

Esercizio: WeakMap per dati privati

exercises/weakmaps/weakmaps_private_data_test.mjs

34.4 API WeakMap

Il costruttore e i quattro metodi di WeakMap funzionano come i loro equivalenti 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

Quiz

Vedi applicazione quiz.

Avanti: 35 Set (Set)