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 keyMé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, ouundefinedse 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)); // falseSem 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]); // undefinedSe 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)); // falseSua 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
| Capacidade | Map / Set | WeakMap / WeakSet |
|---|---|---|
| Chaves / membros | Qualquer valor | Apenas objects |
| Força da referência | Forte (mantém chaves vivas) | Fraca (permite GC) |
size, clear() | Sim | Não |
Iteração (forEach, keys…) | Sim | Não |
| Ideal para | Coleções gerais | Dados 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.