JavaScript WeakMap y WeakSet
En este capítulo encontrarás información completa sobre WeakMap y WeakSet, dos medios útiles para almacenar datos en JavaScript.
Introducción a JavaScript WeakMap y WeakSet
WeakMap y WeakSet son versiones especializadas de Map y Set. Su uso es casi idéntico, pero se diferencian en un aspecto fundamental: mantienen sus claves (o miembros) de forma débil. Una referencia débil no impide que el motor de JavaScript elimine un objeto de la memoria. Si lo único que apunta a un objeto es una clave de WeakMap o un miembro de WeakSet, el objeto puede ser igualmente recolectado por el recolector de basura.
Esa única propiedad define el propósito de WeakMap y WeakSet: permiten asociar datos adicionales a un objeto sin controlar el ciclo de vida de ese objeto. Cuando el objeto desaparece, los datos asociados desaparecen con él — automáticamente, sin ningún código de limpieza.
Este capítulo cubre las reglas que los distinguen, las concesiones que esas reglas imponen y las situaciones reales en las que son la herramienta adecuada.
Qué significa "débil"
En un Map normal, una clave es una referencia fuerte. Mientras exista el Map, todas las claves que contiene se mantienen vivas — aunque ninguna otra parte del programa las utilice. Eso es una fuente habitual de fugas de memoria: si olvidamos hacer delete de una entrada, el objeto clave permanece para siempre.
Un WeakMap invierte esto. La clave se referencia de forma débil, por lo que el motor es libre de reclamar el objeto clave en cuanto nada más haga referencia a él. Cuando eso ocurre, la entrada simplemente desaparece del 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
Un WeakMap es una colección de pares clave/valor donde las claves deben ser objetos y los valores pueden ser cualquier cosa.
Las claves deben ser objetos
Los valores primitivos (string, numbers, booleans, symbol, null, undefined) no pueden ser claves. La razón se desprende del diseño: los primitivos no se recolectan de la misma manera que los objetos, por lo que mantenerlos de forma "débil" no tendría sentido. Intentar usar uno lanza un TypeError.
let wm = new WeakMap();
wm.set("a string", 1); // TypeError: Invalid value used as weak map keyMétodos disponibles
Un WeakMap admite únicamente cuatro operaciones:
set(key, value)— almacena un valor bajo una clave de tipo object.get(key)— lee el valor, oundefinedsi no existe.has(key)— comprueba si existe una clave.delete(key)— elimina una 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)); // falseSin iteración ni tamaño
No existe propiedad size, ni clear(), ni forma de recorrer un WeakMap (no hay keys(), values(), entries() ni forEach). Esto no es un descuido. Dado que las entradas pueden desaparecer en cualquier momento cuando se ejecuta el recolector de basura, el contenido es no determinístico — exponerlo permitiría que el código observara el momento exacto del GC, algo que el lenguaje oculta deliberadamente.
let wm = new WeakMap();
console.log("size" in wm); // false
console.log(wm[Symbol.iterator]); // undefinedSi necesitas contar entradas, iterar o almacenar claves primitivas, usa en su lugar un Map normal.
WeakSet
Un WeakSet es el equivalente a Set: una colección de objetos únicos mantenidos de forma débil. Al igual que WeakMap, sus miembros deben ser objetos, y no ofrece iteración ni 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)); // falseSu API completa es simplemente add(value), has(value) y delete(value).
Casos de uso prácticos
Caché y memoización
Almacena en caché el resultado de un cálculo costoso usando como clave el objeto al que está relacionado. Dado que la clave es débil, la caché nunca mantiene vivo un objeto que ya no se necesita.
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)Datos privados por objeto
Almacena datos que pertenecen a un objeto sin añadir una propiedad a ese objeto (que cualquiera podría leer o sobreescribir). Esta es una forma clásica de emular campos verdaderamente privados, relacionada con cómo los métodos se enlazan a objetos mediante 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.Cuando la instancia de Account ya no se referencia, su entrada de saldo se recolecta automáticamente.
Metadatos para nodos del DOM
Una fuga frecuente en páginas de larga duración es mantener un Map de metadatos de nodos del DOM después de que los nodos hayan sido eliminados del documento. Con WeakSet/WeakMap, una vez que un nodo es desconectado y deja de ser referenciado, sus metadatos también son 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
| Capacidad | Map / Set | WeakMap / WeakSet |
|---|---|---|
| Claves / miembros | Cualquier valor | Solo objetos |
| Fuerza de referencia | Fuerte (mantiene las claves vivas) | Débil (permite GC) |
size, clear() | Sí | No |
Iteración (forEach, keys…) | Sí | No |
| Mejor para | Colecciones generales | Datos asociados a objetos con limpieza automática |
Elige la variante débil solo cuando quieras específicamente que el motor gestione la limpieza por ti y no necesites enumerar el contenido. Para todo lo demás, utiliza el Map y Set normal.