W3docs

JavaScript WeakMap e WeakSet

Neste capítulo, fornecemos informações abrangentes sobre meios úteis de armazenar dados em JavaScript. Aprenda sobre WeakMap e WeakSet aqui.

Introdução ao JavaScript WeakMap e WeakSet

WeakMap e WeakSet são versões especializadas de Map e Set. Elas parecem quase idênticas em uso, mas diferem de uma forma decisiva: elas mantêm suas chaves (ou membros) de forma fraca. Uma referência fraca não impede o motor JavaScript de remover um objeto da memória. Se a única coisa que aponta para um objeto é uma chave de WeakMap ou um membro de WeakSet, o objeto ainda pode ser coletado pelo coletor de lixo.

Essa única propriedade dá ao WeakMap e ao WeakSet o seu propósito: eles permitem associar dados extras a um objeto sem controlar o tempo de vida desse objeto. Quando o objeto desaparece, os dados associados desaparecem com ele — automaticamente, sem nenhum código de limpeza.

Este capítulo aborda as regras que os diferenciam, as trocas que essas regras impõem e as situações reais em que são a ferramenta certa.

O que significa "fraco"

Em um Map regular, uma chave é uma referência forte. Enquanto o Map existir, cada chave que ele mantém é mantida viva — mesmo que nenhuma outra parte do seu programa ainda use essa chave. Isso é uma fonte comum de vazamentos de memória: você esquece de delete uma entrada e o objeto-chave persiste para sempre.

Um WeakMap inverte isso. A chave é referenciada de forma fraca, então o motor é livre para reclamar o objeto-chave assim que nada mais se referir a ele. Quando isso acontece, a entrada simplesmente desaparece do 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

Um WeakMap é uma coleção de pares chave/valor onde as chaves devem ser objetos e os valores podem ser qualquer coisa.

As chaves devem ser objetos

Valores primitivos (strings, numbers, booleans, symbol, null, undefined) não podem ser chaves. O motivo decorre do design: primitivos não são coletados pelo coletor de lixo da mesma forma que objetos, portanto "mantê-los fracamente" não faz sentido. Tentar usar um lança um TypeError.

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

Métodos disponíveis

Um WeakMap suporta apenas quatro operações:

  • set(key, value) — armazena um valor sob uma chave de object.
  • get(key) — lê o valor, ou undefined se ausente.
  • has(key) — verifica se uma chave existe.
  • delete(key) — remove uma entrada.
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

Sem iteração e sem tamanho

Não há propriedade size, nem clear(), nem forma de percorrer um WeakMap (sem keys(), values(), entries() ou forEach). Isso não é uma omissão. Como as entradas podem desaparecer a qualquer momento quando o coletor de lixo é executado, o conteúdo é não determinístico — expô-lo permitiria que seu código observasse o tempo do GC, o que a linguagem deliberadamente oculta.

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

Se você precisar contar entradas, iterar ou armazenar chaves primitivas, use um Map regular em seu lugar.

WeakSet

Um WeakSet é o equivalente a Set: uma coleção de objetos únicos mantidos de forma fraca. Como WeakMap, seus membros devem ser objetos e não oferece iteração ou 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

Sua API completa é apenas add(value), has(value) e delete(value).

Casos de uso práticos

Cache e memoização

Armazene em cache o resultado de um cálculo dispendioso com chave no object ao qual ele está relacionado. Como a chave é fraca, o cache nunca mantém um objeto que não é mais necessário vivo.

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)

Dados privados por objeto

Armazene dados que pertencem a um object sem adicionar uma propriedade ao próprio objeto (que qualquer um poderia ler ou sobrescrever). Esta é uma forma clássica de emular campos verdadeiramente privados, relacionada a como os métodos se vinculam a objetos via 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 a instância de Account não for mais referenciada, sua entrada de saldo será coletada automaticamente.

Metadados para nós do DOM

Um vazamento frequente em páginas de longa duração é manter um Map de metadados de nós DOM após os nós serem removidos do documento. Com WeakSet/WeakMap, assim que um nó é desconectado e desreferenciado, seus metadados também são recuperados.

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

CapacidadeMap / SetWeakMap / WeakSet
Chaves / membrosQualquer valorApenas objects
Força da referênciaForte (mantém chaves vivas)Fraca (permite GC)
size, clear()SimNão
Iteração (forEach, keys…)SimNão
Ideal paraColeções geraisDados associados a objetos com limpeza automática

Escolha a variante fraca somente quando quiser especificamente que o motor gerencie a limpeza para você e não precisar enumerar o conteúdo. Para todo o resto, use o Map e Set regular.

Prática

Prática
Quais são algumas características principais de WeakMap e WeakSet em JavaScript?
Quais são algumas características principais de WeakMap e WeakSet em JavaScript?
Was this page helpful?