W3docs

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 key

Mé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, o undefined si 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));   // false

Sin 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]);   // undefined

Si 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));   // false

Su 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

CapacidadMap / SetWeakMap / WeakSet
Claves / miembrosCualquier valorSolo objetos
Fuerza de referenciaFuerte (mantiene las claves vivas)Débil (permite GC)
size, clear()No
Iteración (forEach, keys…)No
Mejor paraColecciones generalesDatos 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.

Práctica

Práctica
¿Cuáles son algunas características clave de WeakMap y WeakSet en JavaScript?
¿Cuáles son algunas características clave de WeakMap y WeakSet en JavaScript?
Was this page helpful?