Skip to content

Lección 50: WeakMap y WeakSet

WeakMap y WeakSet son versiones “débiles” de Map y Set que permiten que los objetos sean recolectados por el garbage collector cuando ya no hay otras referencias a ellos.

Características principales:

  • Solo acepta objetos como claves (no primitivos)
  • Las referencias a las claves son débiles: si no hay otras referencias al objeto, puede ser recolectado
  • No tiene .size (no sabemos cuántos elementos hay)
  • No es iterable (no podemos recorrerlo)
  • Métodos: .set(), .get(), .has(), .delete()
const weakMap = new WeakMap();
let usuario = { nombre: 'Ana' };
weakMap.set(usuario, 'información privada');
console.log(weakMap.has(usuario)); // true
console.log(weakMap.get(usuario)); // "información privada"
// Si eliminamos la referencia al objeto...
usuario = null;
// ... el WeakMap libera la entrada automáticamente (GC)
// No podemos verificarlo porque no hay .size ni iteración

Características similares a WeakMap:

  • Solo acepta objetos (no primitivos)
  • Referencias débiles
  • No tiene .size
  • No es iterable
  • Métodos: .add(), .has(), .delete()
const weakSet = new WeakSet();
let elemento = { id: 1 };
weakSet.add(elemento);
console.log(weakSet.has(elemento)); // true
elemento = null; // el WeakSet libera la entrada
// Caso de uso: metadatos privados para objetos
const metadatos = new WeakMap();
class Usuario {
constructor(nombre) {
this.nombre = nombre;
// Guardar datos privados en WeakMap
metadatos.set(this, {
creado: new Date(),
ultimoAcceso: null,
contadorAccesos: 0
});
}
acceder() {
const datos = metadatos.get(this);
datos.ultimoAcceso = new Date();
datos.contadorAccesos++;
return `Bienvenido ${this.nombre} (acceso #${datos.contadorAccesos})`;
}
getInfoPrivada() {
return metadatos.get(this);
}
}
const usuario = new Usuario('Ana');
console.log(usuario.acceder()); // "Bienvenido Ana (acceso #1)"
console.log(usuario.acceder()); // "Bienvenido Ana (acceso #2)"
console.log(usuario.getInfoPrivada()); // { creado: Date, ultimoAcceso: Date, contadorAccesos: 2 }
// Cuando usuario deje de existir, el WeakMap libera su metadata automáticamente
// Caso de uso: evitar fugas de memoria con listeners
const listeners = new WeakMap();
function agregarListener(elemento, evento, callback) {
if (!listeners.has(elemento)) {
listeners.set(elemento, new Map());
}
const elementoListeners = listeners.get(elemento);
elementoListeners.set(evento, callback);
elemento.addEventListener(evento, callback);
}
// Cuando el elemento se elimina del DOM, los listeners se limpian solos

¿Por qué no tienen .size ni iteración? Porque las referencias débiles pueden ser recolectadas en cualquier momento. Si pudieras iterar, verías entradas que desaparecen mientras iteras — inconsistencia.

No confundir “débil” con “débilmente tipado”. WeakMap/WeakSet se refiere a las referencias débiles para el garbage collector.

Los tipos primitivos como claves en WeakMap dan error:

const wm = new WeakMap();
// wm.set('string', 1); // TypeError: Invalid value used as weak map key
// wm.set(123, 1); // TypeError

Casos de uso reales para WeakMap/WeakSet:

  1. Datos privados para objetos sin exponerlos (el patrón de arriba)
  2. Evitar fugas de memoria en cachés de objetos DOM
  3. Marcar objetos sin modificar el objeto original (WeakSet)
  4. Metadatos asociados a elementos que se crean y destruyen dinámicamente
// Marcar objetos visitados (sin modificar el objeto)
const visitados = new WeakSet();
function visitar(objeto) {
if (visitados.has(objeto)) {
console.log('Ya visitado');
} else {
visitados.add(objeto);
console.log('Visitando por primera vez');
}
}
const obj1 = { id: 1 };
const obj2 = { id: 2 };
visitar(obj1); // "Visitando por primera vez"
visitar(obj1); // "Ya visitado"
visitar(obj2); // "Visitando por primera vez"

Crea un sistema de caché con WeakMap que almacene resultados de cálculos para objetos. Cuando el objeto se elimina, el caché debe liberarse automáticamente.

const calculosCache = new WeakMap();
function calcularComplejo(objeto, numero) {
// Si ya existe el resultado en caché para este objeto, devuélvelo
// Si no, calcula (numero * numero), guárdalo en caché y devuélvelo
// Tu código aquí
}
const obj = { nombre: 'test' };
console.log(calcularComplejo(obj, 5)); // 25 (calculado)
console.log(calcularComplejo(obj, 5)); // 25 (desde caché — sin calcular)