WeakMap y WeakSet: colecciones con referencias débiles

Avanzado
JavaScript
JavaScript
Actualizado: 04/05/2026

Diagrama: WeakMap y WeakSet en JavaScript

Qué es una referencia débil

En JavaScript la memoria se libera mediante un recolector de basura (garbage collector, GC): un objeto sobrevive mientras alguien mantenga una referencia a él. Si un objeto queda "colgado" en un Map o un Set, aunque ya no lo uses en ninguna otra parte del código, sigue vivo porque la colección lo referencia.

El siguiente diagrama muestra el modelo de memoria del motor V8 y cómo deciden el recolector y las colecciones débiles qué objetos liberar. El stack contiene primitivos y referencias de ejecución; el heap aloja objetos y cadenas, organizados en una generación joven (Young: Eden + two semi-spaces, colección Scavenge frecuente) y una generación vieja (Old Space, Mark-Sweep-Compact). Los algoritmos generacionales se basan en la hipótesis de que la mayoría de objetos mueren jóvenes. Las referencias dentro de WeakMap y WeakSet se marcan como débiles y no cuentan para mantener vivo un objeto.

Las referencias débiles son un tipo especial de referencia que no protegen al objeto frente al GC. Es decir: si la única forma de llegar a un objeto es a través de una referencia débil, el GC puede recogerlo en cualquier momento. WeakMap y WeakSet son las colecciones que emplean este tipo de referencia para sus claves (WeakMap) o elementos (WeakSet).

El efecto práctico es muy útil: podemos asociar datos a una instancia sin preocuparnos de limpiarlos manualmente cuando la instancia deje de existir.

WeakMap: clave con valor y clave débil

Un WeakMap funciona como un Map, pero con dos restricciones y una propiedad distintiva:

  • Las claves deben ser objetos (no primitivos).
  • Las entradas no son enumerables: no hay keys, values, entries, size ni forEach.
  • Las claves se referencian débilmente: si el objeto-clave deja de tener otras referencias, la entrada desaparece.
const metadata = new WeakMap();

let user = { id: 1, name: "Alan" };
metadata.set(user, { lastLogin: Date.now(), roles: ["admin"] });

console.log(metadata.has(user)); // true
console.log(metadata.get(user)); // { lastLogin: ..., roles: ["admin"] }

user = null; // La única referencia al objeto se pierde
// La entrada del WeakMap podrá ser recogida por el GC.

No existe un API para forzar la recolección, ni para comprobar cuándo ocurre. Simplemente: la implementación garantiza que no habrá fuga.

Caso práctico: caché por instancia

Imagina que quieres calcular algo caro a partir de un objeto y cachear el resultado. Un Map te obligaría a invalidar la entrada cuando dejaras de usar el objeto. Con un WeakMap no hace falta:

const cache = new WeakMap();

function getExpensiveData(user) {
  if (cache.has(user)) {
    return cache.get(user);
  }
  const data = computeExpensiveData(user); // Llamada costosa
  cache.set(user, data);
  return data;
}

function computeExpensiveData(user) {
  console.log(`Computing data for ${user.name}`);
  return { summary: `User ${user.name}` };
}

let alan = { name: "Alan" };
getExpensiveData(alan); // Computing data for Alan
getExpensiveData(alan); // cache hit, sin log

alan = null; // El usuario queda fuera de alcance
// La entrada del cache desaparecerá con el GC, sin código explícito de limpieza.

Caso práctico: metadatos "privados"

Antes de los campos privados (#campo), un patrón habitual para simular privacidad consistía en guardar el estado en un WeakMap exterior a la clase:

const privateState = new WeakMap();

class Counter {
  constructor(initial = 0) {
    privateState.set(this, { count: initial });
  }

  increment() {
    const state = privateState.get(this);
    state.count += 1;
  }

  get value() {
    return privateState.get(this).count;
  }
}

const c = new Counter(10);
c.increment();
console.log(c.value); // 11

Al hacerse a través del WeakMap, el estado no se expone como propiedad del objeto: no aparece en Object.keys, no viaja en JSON.stringify, y se libera automáticamente cuando la instancia desaparece.

WeakSet: pertenencia con referencia débil

WeakSet es la versión de Set con referencias débiles. Sus elementos deben ser objetos y tampoco se puede iterar ni consultar su tamaño.

const visited = new WeakSet();

function walk(node) {
  if (visited.has(node)) return; // Evita ciclos
  visited.add(node);

  for (const child of node.children ?? []) {
    walk(child);
  }
}

Al igual que con el WeakMap, si un nodo deja de usarse, su entrada en el WeakSet desaparecerá.

Caso práctico: marcar instancias como procesadas

Un uso habitual es registrar que un objeto ya ha sido inicializado o validado, sin tener que añadirle una propiedad especial:

const initialized = new WeakSet();

function init(obj) {
  if (initialized.has(obj)) return;
  // … trabajo de inicialización …
  initialized.add(obj);
}

const widget = { id: "w-1" };
init(widget); // primera vez: se inicializa
init(widget); // segunda vez: no-op

El objeto queda intacto (no añadimos propiedades), y la marca se pierde cuando el objeto desaparece.

Caso práctico: validar que el receptor es una instancia permitida

Con módulos que exportan clases y fábricas, a veces se usa un WeakSet como "carnet" para comprobar que un método recibe un objeto auténtico y no un duck-typed:

const officialInstances = new WeakSet();

export class Account {
  constructor(id) {
    this.id = id;
    officialInstances.add(this);
  }
}

export function withdraw(account, amount) {
  if (!officialInstances.has(account)) {
    throw new TypeError("Not an official Account instance");
  }
  // … retirada segura …
}

Diferencias con Map y Set

| Operación | Map / Set | WeakMap / WeakSet | |-----------|---------------|------------------------| | Claves / elementos | Cualquier tipo | Solo objetos | | Referencias | Fuertes | Débiles | | Iteración (for...of, forEach) | Sí | No | | size | Sí | No | | clear() | Sí | No | | Recolección automática | No | Sí, cuando el objeto deja de ser referenciado |

La tabla deja claro cuándo usar cada uno. Las versiones Weak se centran exclusivamente en el caso "asociar información a un objeto mientras exista", y para ganar esa garantía de recolección se renuncia a la enumeración.

Cuándo no usarlas

WeakMap y WeakSet no son sustitutos generales de Map/Set. Evítalos cuando:

  • Necesitas iterar o conocer el tamaño: no lo permiten.
  • Las claves son primitivos (cadenas, números, Symbols): no las admiten.
  • Necesitas serializar el contenido: al no ser iterables, no hay forma directa.
  • Quieres una caché con política propia de expiración (TTL, LRU...): usa Map y controla el ciclo de vida tú mismo.

Un error frecuente es intentar "contar" cuántas entradas tiene un WeakMap para inferir qué objetos siguen vivos. No es posible ni fiable: la recolección es asíncrona y no observable.

Buenas prácticas

  • Usa WeakMap cuando quieras asociar datos a un objeto sin modificarlo ni prolongar su vida.
  • Usa WeakSet para marcadores booleanos sobre objetos (procesado, visitado, instancia oficial).
  • No confíes en momentos concretos de recolección; el GC es libre de no ejecutarse nunca si no hace falta.
  • Si necesitas iterar o conocer el tamaño, cambia a Map/Set y encárgate tú de eliminar entradas obsoletas.
  • Para lógica más fina (notificación cuando un objeto es recogido), existe WeakRef + FinalizationRegistry, pero se recomienda solo para casos muy específicos (cachés de recursos externos) porque su comportamiento es intencionalmente no determinista.

Resumen

WeakMap y WeakSet son colecciones pensadas para un problema concreto: asociar información a objetos sin alargarles la vida. Sus referencias débiles permiten que el recolector de basura recupere los objetos cuando nadie más los usa, y con ellos desaparecen las entradas correspondientes. A cambio se pierde la capacidad de iterar o medir la colección. Son la herramienta correcta para cachés por instancia, metadatos privados y marcadores como visited o initialized; para cualquier otro caso, Map y Set siguen siendo la elección adecuada.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, JavaScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de JavaScript

Explora más contenido relacionado con JavaScript y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Entender qué es una referencia débil y cómo afecta al recolector de basura.
  • Diferenciar WeakMap/WeakSet de Map/Set en API y comportamiento.
  • Usar WeakMap para asociar metadatos privados a instancias sin fugas de memoria.
  • Usar WeakSet para marcar objetos visitados o con un atributo booleano.
  • Implementar una caché por objeto que se libere cuando desaparezca la clave.
  • Reconocer los límites: no se puede iterar ni conocer el tamaño.