WeakMap y WeakSet en JavaScript: referencias débiles | Nicolás Garzón
WeakMap y WeakSet almacenan relaciones débiles con sus claves o valores.
Una relación débil no obliga al garbage collector a mantener vivo un objeto únicamente porque aparece dentro de la colección.
JavaScript
Copiar const metadata = new WeakMap ( ) ;
let user = {
id : "user-1" ,
} ;
metadata. set ( user, {
lastAccess : Date. now ( ) ,
} ) ; JavaScript
Copiar const map = new Map ( ) ;
let user = { id : 1 } ;
map. set ( user, "metadata" ) ;
user = null ; El Map todavía conserva una referencia fuerte a la clave.
JavaScript
Copiar const weakMap = new WeakMap ( ) ;
let user = { id : 1 } ;
weakMap. set ( user, "metadata" ) ;
user = null ; Si no existe otra referencia fuerte al objeto, la entrada puede desaparecer cuando el motor realice garbage collection.
No puedes observar ni controlar el instante exacto.
JavaScript
Copiar user = null ; Esta asignación no ejecuta obligatoriamente el garbage collector en ese momento.
Depende del motor.
Puede ocurrir más tarde.
Puede no ocurrir antes de que termine el proceso.
No debe utilizarse como reloj ni mecanismo de negocio.
Una colección débil ayuda a no extender la vida de un objeto; no ofrece eventos confiables de destrucción.
Las claves de WeakMap y los valores de WeakSet deben ser valores que puedan mantenerse débilmente.
En la práctica se utilizan principalmente objetos y funciones.
JavaScript
Copiar const metadata = new WeakMap ( ) ;
metadata. set ( { } , "valid" ) ;
metadata. set ( function handler ( ) { } , "valid" ) ; El estándar moderno también permite symbols no registrados globalmente.
JavaScript
Copiar const token = Symbol ( "token" ) ;
const map = new WeakMap ( [ [ token, "metadata" ] ] ) ; Un symbol registrado mediante Symbol.for no es una clave débil válida porque el registro global conserva su identidad.
JavaScript
Copiar const sharedToken = Symbol. for ( "token" ) ;
Los primitivos ordinarios no son válidos.
JavaScript
Copiar weakMap. set ( "user-1" , value) ; JavaScript
Copiar const metadata = new WeakMap ( ) ;
metadata. set ( object, value) ;
metadata. get ( object) ;
metadata. has ( object) ;
metadata. delete ( object) ;
size.
clear.
keys, values o entries.
Iteración con for...of.
forEach.
Supón que pudiera enumerarse:
JavaScript
Copiar for ( const [ key, value] of weakMap) {
} El resultado dependería de si el garbage collector ya eliminó ciertas claves. Ese momento no es observable ni determinista.
Para evitar que el programa dependa de detalles internos de memoria, las colecciones débiles no exponen enumeración ni tamaño.
La clave es débil; el valor asociado puede ser cualquier valor.
JavaScript
Copiar metadata. set ( user, {
permissions : [ "read" , "write" ] ,
} ) ; Mientras la clave siga viva y la entrada exista, el valor puede permanecer accesible mediante esa relación.
Si el valor conserva una referencia hacia la clave, los motores pueden tratar el ciclo correctamente; lo importante es si existe una referencia fuerte desde fuera del grupo.
JavaScript
Copiar const validationState = new WeakMap ( ) ;
function markValidated ( object ) {
validationState. set ( object, {
validatedAt : Date. now ( ) ,
} ) ;
}
function isValidated ( object ) {
return validationState. has ( object) ;
} La información queda asociada al objeto sin agregarle propiedades visibles.
Cuando el objeto ya no se utiliza, el registro no necesita una limpieza manual obligatoria.
JavaScript
Copiar const privateState = new WeakMap ( ) ;
class Counter {
constructor ( ) {
privateState. set ( this , {
value : 0 ,
} ) ;
}
increment ( ) {
privateState. get ( this ) . value++ ;
}
read ( ) {
return privateState. get ( this ) . value;
}
} Este patrón todavía puede servir cuando:
Debes asociar estado desde un módulo externo.
Varias funciones necesitan datos privados compartidos.
No puedes modificar la clase u objeto original.
Para privacidad interna de una clase moderna, los campos # suelen ser más directos.
JavaScript
Copiar const calculatedLayouts = new WeakMap ( ) ;
function calculateLayout ( element ) {
if ( calculatedLayouts. has ( element) ) {
return calculatedLayouts. get ( element) ;
}
const layout = expensiveCalculation ( element) ;
calculatedLayouts. set ( element, layout) ;
return layout;
} Cuando el elemento deja de existir en la aplicación y el DOM, la cache no tiene por qué mantenerlo vivo.
Esto no añade expiración temporal ni límite de tamaño observable; resuelve una relación de vida ligada a la clave.
El estándar ECMAScript 2026 incorpora:
JavaScript
Copiar weakMap. getOrInsert ( key, defaultValue) ; JavaScript
Copiar weakMap. getOrInsertComputed (
key,
( ) => createMetadata ( key) ,
) ; Crean la entrada únicamente cuando la clave no existe.
Son APIs recientes. Comprueba compatibilidad antes de utilizarlas en runtimes antiguos.
Un WeakSet mantiene valores débiles sin un dato asociado.
JavaScript
Copiar const processedObjects = new WeakSet ( ) ;
processedObjects. add ( object) ;
processedObjects. has ( object) ;
processedObjects. delete ( object) ; No ofrece tamaño, limpieza total ni iteración.
JavaScript
Copiar const processed = new WeakSet ( ) ;
function processNode ( node ) {
if ( processed. has ( node) ) {
return ;
}
processed. add ( node) ;
performWork ( node) ;
} El marcador no obliga al programa a conservar nodos que ya no utiliza.
JavaScript
Copiar function serialize ( value, visited = new WeakSet ( ) ) {
if ( value === null || typeof value !== "object" ) {
return value;
}
if ( visited. has ( value) ) {
return "[Circular]" ;
}
visited. add ( value) ;
const result = { } ;
for ( const [ key, nestedValue] of Object. entries ( value) ) {
result[ key] = serialize (
nestedValue,
visited,
) ;
}
return result;
} WeakSet registra qué objetos fueron visitados sin convertirse necesariamente en el dueño permanente de esas referencias.
Dentro de una sola llamada también podría usarse Set; la elección depende del tiempo de vida y de si necesitas inspeccionar la colección.
Usa Map cuando necesitas:
Iterar entradas.
Conocer el tamaño.
Limpiar toda la colección.
Claves primitivas comunes.
Persistir o inspeccionar el contenido.
Las claves son objetos o valores débiles admitidos.
La entrada debe vivir aproximadamente mientras viva la clave.
No necesitas enumerar el conjunto completo.
Quieres evitar que el registro sea la única razón para mantener vivo el objeto.
Usa Set cuando necesitas:
Enumerar valores.
Operaciones de conjuntos.
Conocer el tamaño.
Guardar primitivos.
Usa WeakSet cuando solo necesitas preguntar si un objeto vivo está marcado y no deseas mantenerlo vivo por ese marcador.
Una colección débil no es adecuada para:
Sesiones que debes listar.
Registros de negocio.
Datos que deben serializarse.
Inventarios o permisos persistentes.
Métricas que necesitan contar entradas.
Caches que requieren inspección o política LRU.
Sus entradas pueden desaparecer cuando sus claves dejan de ser accesibles.
JavaScript ofrece FinalizationRegistry para solicitar una callback después de que ciertos valores sean recolectados.
No debe utilizarse para lógica esencial:
La callback puede tardar.
Puede no ejecutarse antes de terminar el proceso.
El orden no es confiable.
No sustituye close, dispose o limpieza explícita de recursos.
WeakMap y WeakSet suelen ser útiles sin observar la recolección.
JavaScript
Copiar const componentState = new WeakMap ( ) ;
function initializeComponent ( element ) {
componentState. set ( element, {
isOpen : false ,
mountedAt : Date. now ( ) ,
} ) ;
}
function toggleComponent ( element ) {
const state = componentState. get ( element) ;
if ( ! state) {
throw new Error (
"Component is not initialized" ,
) ;
}
state. isOpen = ! state. isOpen;
return state. isOpen;
} El estado pertenece al tiempo de vida del elemento, pero no se añade como propiedad pública sobre él.
Pensar que la entrada se elimina inmediatamente al asignar null.
Esperar iteración o una propiedad size.
Utilizar strings o números como claves.
Construir reglas de negocio alrededor del momento de garbage collection.
Usar WeakMap cuando necesitas listar o serializar entradas.
Creer que es una cache con expiración automática.
Presentar todos los symbols como claves válidas sin distinguir los registrados globalmente.
Usar FinalizationRegistry para liberar recursos críticos.
Preferir WeakMap a campos privados sin una razón concreta.
Las colecciones débiles no mantienen viva una clave únicamente por contenerla.
El momento de garbage collection no puede observarse de forma confiable.
WeakMap relaciona una clave débil con cualquier valor.
WeakSet marca valores débiles sin datos asociados.
No tienen tamaño, iteración ni limpieza total.
Se usan principalmente con objetos; el estándar también admite symbols no registrados.
Son útiles para metadatos, estado externo, caches ligadas a objetos y detección de ciclos.
No sustituyen Map, Set ni almacenamiento persistente.
¿Por qué WeakMap no ofrece keys() ni size?
Respuesta Porque sus claves pueden desaparecer según decisiones no deterministas del garbage collector. Exponer enumeración o tamaño permitiría que el programa observara ese proceso y dependiera de él.
Iterables e iteradores explica el protocolo que permite a arrays, Maps, Sets, strings y otras secuencias entregar valores uno a uno.