W3docs

JavaScript WeakMap e WeakSet

In questo capitolo forniamo informazioni complete su WeakMap e WeakSet, strumenti utili per archiviare dati in JavaScript.

Introduzione a JavaScript WeakMap e WeakSet

WeakMap e WeakSet sono versioni specializzate di Map e Set. Dal punto di vista dell'utilizzo sono quasi identici, ma si differenziano in un aspetto decisivo: conservano le loro chiavi (o membri) in modo debole. Un riferimento debole non impedisce al motore JavaScript di rimuovere un object dalla memoria. Se l'unica cosa che punta a un object è una chiave di WeakMap o un membro di WeakSet, l'object può comunque essere sottoposto a garbage collection.

Questa singola proprietà conferisce a WeakMap e WeakSet il loro scopo: permettono di associare dati aggiuntivi a un object senza controllarne il ciclo di vita. Quando l'object scompare, i dati associati scompaiono con lui — automaticamente, senza alcun codice di pulizia.

Questo capitolo tratta le regole che li distinguono, i compromessi che tali regole impongono e le situazioni reali in cui sono lo strumento giusto.

Cosa significa "debole"

In una Map normale, una chiave è un riferimento forte. Finché la Map esiste, ogni chiave che contiene viene mantenuta in vita — anche se nessun'altra parte del programma la utilizza. Questa è una fonte comune di perdite di memoria: si dimentica di usare delete su una voce e l'object chiave rimane per sempre.

Una WeakMap inverte questo comportamento. La chiave è referenziata debolmente, quindi il motore è libero di recuperare l'object chiave non appena nient'altro vi fa riferimento. Quando ciò avviene, la voce scompare semplicemente dalla WeakMap.

let visits = new WeakMap();
let user = { name: "Alice" };
visits.set(user, 10);

console.log(visits.get(user));   // 10
console.log(visits.has(user));   // true

user = null; // the object is now unreachable elsewhere
// The WeakMap entry becomes eligible for garbage collection.
// You cannot observe exactly when it is removed.

WeakMap

Una WeakMap è una raccolta di coppie chiave/valore in cui le chiavi devono essere object e i valori possono essere di qualsiasi tipo.

Le chiavi devono essere object

I valori primitivi (string, numbers, boolean, symbol, null, undefined) non possono essere chiavi. La ragione deriva dalla progettazione: i primitivi non vengono sottoposti a garbage collection come gli object, quindi "tenerli debolmente" non ha senso. Il tentativo di usarne uno genera un TypeError.

let wm = new WeakMap();
wm.set("a string", 1); // TypeError: Invalid value used as weak map key

Metodi disponibili

Una WeakMap supporta solo quattro operazioni:

  • set(key, value) — memorizza un valore associato a una chiave object.
  • get(key) — legge il valore, o undefined se assente.
  • has(key) — verifica se una chiave esiste.
  • delete(key) — rimuove una voce.
let wm = new WeakMap();
let key = { id: 1 };

wm.set(key, "data");
console.log(wm.has(key));   // true
console.log(wm.get(key));   // "data"

wm.delete(key);
console.log(wm.has(key));   // false

Nessuna iterazione e nessuna dimensione

Non esiste né la proprietà size, né clear(), né alcun modo per iterare una WeakMap (nessun keys(), values(), entries() o forEach). Non si tratta di una svista. Poiché le voci possono scomparire in qualsiasi momento quando il garbage collector viene eseguito, i contenuti sono non deterministici — esporli permetterebbe al codice di osservare i tempi del GC, cosa che il linguaggio nasconde deliberatamente.

let wm = new WeakMap();
console.log("size" in wm);          // false
console.log(wm[Symbol.iterator]);   // undefined

Se è necessario contare le voci, iterare o archiviare chiavi primitive, utilizzare una Map normale.

WeakSet

Un WeakSet è l'equivalente di Set: una raccolta di object unici conservati debolmente. Come WeakMap, i suoi membri devono essere object e non offre iterazione né size.

let visited = new WeakSet();
let a = { id: 1 };
let b = { id: 2 };

visited.add(a);
console.log(visited.has(a));   // true
console.log(visited.has(b));   // false

visited.delete(a);
console.log(visited.has(a));   // false

La sua API completa è semplicemente add(value), has(value) e delete(value).

Casi d'uso pratici

Caching e memoization

Memorizza nella cache il risultato di un'operazione costosa utilizzando come chiave l'object a cui si riferisce. Poiché la chiave è debole, la cache non mantiene mai in vita un object non più necessario.

const cache = new WeakMap();

function process(obj) {
  if (cache.has(obj)) {
    return cache.get(obj); // reuse the cached result
  }
  const result = obj.value * 2; // pretend this is expensive
  cache.set(obj, result);
  return result;
}

let data = { value: 21 };
console.log(process(data));   // 42  (computed)
console.log(process(data));   // 42  (from cache)

Dati privati per object

Archivia dati che appartengono a un object senza aggiungere una proprietà all'object stesso (che chiunque potrebbe leggere o sovrascrivere). Questo è un modo classico per emulare campi veramente privati, correlato a come i metodi si legano agli object tramite this.

const balances = new WeakMap();

class Account {
  constructor(amount) {
    balances.set(this, amount); // private to this module
  }
  deposit(n) {
    balances.set(this, balances.get(this) + n);
  }
  get balance() {
    return balances.get(this);
  }
}

const acc = new Account(100);
acc.deposit(50);
console.log(acc.balance);   // 150
// There is no `amount` property on `acc` itself to tamper with.

Quando l'istanza di Account non è più referenziata, la sua voce del saldo viene raccolta automaticamente.

Metadati per i nodi DOM

Una perdita frequente nelle pagine a lunga esecuzione consiste nel mantenere una Map di metadati dei nodi DOM dopo che i nodi vengono rimossi dal documento. Con un WeakSet/WeakMap, una volta che un nodo viene scollegato e dereferenziato, i suoi metadati vengono recuperati anch'essi.

const seen = new WeakSet();

function markVisited(node) {
  if (seen.has(node)) return false; // already processed
  seen.add(node);
  return true;
}
// When the node leaves the DOM and nothing else holds it,
// it disappears from `seen` without manual cleanup.

WeakMap/WeakSet vs Map/Set

CapacitàMap / SetWeakMap / WeakSet
Chiavi / membriQualsiasi valoreSolo object
Forza del riferimentoForte (mantiene le chiavi in vita)Debole (consente il GC)
size, clear()No
Iterazione (forEach, keys…)No
Ideale perRaccolte generaliDati associati a object con pulizia automatica

Scegli la variante debole solo quando vuoi specificamente che il motore gestisca la pulizia per te e non hai bisogno di enumerare i contenuti. Per tutto il resto, utilizza la normale Map e Set.

Esercizio

Pratica
Quali sono alcune caratteristiche chiave di WeakMap e WeakSet in JavaScript?
Quali sono alcune caratteristiche chiave di WeakMap e WeakSet in JavaScript?
Was this page helpful?