Ir al contenido
angel.library

Map, Set y colecciones débiles

Usa Map para claves dinámicas de cualquier tipo, Set para pertenencia y valores únicos, y colecciones débiles para metadata cuya vida útil siga a otro objeto. Map y Set son iterables y mutables; WeakMap y WeakSet no se pueden enumerar.

EstructuraClaves o valoresOrdenCaso principal
Objectclaves string o symbolorden definido con reglas propiasregistros con campos conocidos
Mapclaves de cualquier tipoinsercióndiccionario dinámico y recorrible
Setvalores únicos de cualquier tipoinsercióneliminar duplicados y pertenencia
WeakMapclaves que sean objetos o symbols no registradosno recorriblemetadatos privados ligados a un objeto
WeakSetobjetos o symbols no registradosno recorriblemarcar objetos sin impedir su recolección

Map y Set son mutables. Sus métodos de escritura cambian la instancia; si necesitas conservar el valor anterior, crea otra colección de forma explícita.

MiembroDevuelveMutaCaso de uso
sizecantidad de entradasnoconocer el tamaño
set(key, value)el mismo Mapsícrear o reemplazar una entrada
get(key)valor o undefinednoleer una entrada
getOrInsert(key, defaultValue)valor existente o insertadosí si faltaobtener con valor predeterminado
getOrInsertComputed(key, callback)valor existente o calculadosí si faltacrear el valor solo cuando sea necesario
has(key)booleanonocomprobar existencia sin confundir undefined
delete(key)booleanosíeliminar y saber si existía
clear()undefinedsívaciar el Map
keys()iterador de clavesnorecorrer claves
values()iterador de valoresnorecorrer valores
entries()iterador [key, value]norecorrer pares
forEach(callback)undefinedno por sí soloejecutar un efecto por entrada
const visits = new Map()
visits.set("home", 3) // Map(1) {'home' => 3}
visits.set("docs", 8) // Map(2) {'home' => 3, 'docs' => 8}
visits.get("docs") // 8
visits.get("missing") // undefined
visits.has("home") // true
visits.size // 2
visits.delete("home") // true
visits.delete("home") // false
;[...visits.entries()] // [['docs', 8]]

Las claves conservan su tipo y su identidad:

const objectKey = { id: 1 }
const cache = new Map()
cache.set(objectKey, "resultado")
cache.set(1, "número")
cache.set("1", "texto")
cache.get(objectKey) // 'resultado'
cache.get({ id: 1 }) // undefined: es otra referencia
cache.size // 3
const map = new Map([
["theme", "dark"],
["compact", true]
])
Object.fromEntries(map)
// { theme: 'dark', compact: true }
new Map(Object.entries({ page: 1, limit: 20 }))
// Map(2) {'page' => 1, 'limit' => 20}

Map.groupBy agrupa un iterable conservando la identidad y el tipo de la clave:

const free = { name: "free" }
const pro = { name: "pro" }
const users = [
{ name: "Ana", plan: pro },
{ name: "Leo", plan: free },
{ name: "Mara", plan: pro }
]
const byPlan = Map.groupBy(users, (user) => user.plan)
byPlan.get(pro).map((user) => user.name)
// ['Ana', 'Mara']

El resultado usa las mismas referencias de plan y de los usuarios. Para claves string, Object.groupBy puede producir una forma más fácil de serializar.

ECMAScript 2026 añadió operaciones conocidas como upsert: leer una clave existente o insertar una alternativa cuando falta. getOrInsert recibe el valor directamente; getOrInsertComputed ejecuta un callback únicamente si debe crearlo.

const projectsByOwner = new Map()
projectsByOwner.getOrInsert("ana", []).push("library")
projectsByOwner.getOrInsertComputed("leo", (owner) => [`welcome-${owner}`])
projectsByOwner.get("ana") // ['library']
projectsByOwner.get("leo") // ['welcome-leo']

Prefiere la versión calculada cuando construir el valor cuesta trabajo o tiene efectos. En getOrInsert(key, createValue()), createValue() se ejecuta antes de llamar al método aunque la clave ya exista. WeakMap ofrece los mismos dos métodos para claves débiles.

MiembroDevuelveMutaCaso de uso
sizecantidad de valoresnoconocer el tamaño
add(value)el mismo Setsíañadir si no existe
has(value)booleanonopertenencia rápida
delete(value)booleanosíquitar un valor
clear()undefinedsívaciar el Set
keys(), values()iterador de valoresnorecorrer valores
entries()iterador [value, value]nocompatibilidad con Map
const tags = new Set(["js", "web", "js"])
tags.size // 2
tags.add("dom") // Set(3) {'js', 'web', 'dom'}
tags.has("web") // true
tags.delete("js") // true
;[...tags] // ['web', 'dom']

Eliminar duplicados es una de sus aplicaciones más frecuentes:

const repeated = ["js", "css", "js", "html", "css"]
const unique = [...new Set(repeated)]
unique // ['js', 'css', 'html']

Con objetos, la unicidad también depende de la referencia:

new Set([{ id: 1 }, { id: 1 }]).size // 2
const item = { id: 1 }
new Set([item, item]).size // 1

Los métodos modernos de Set devuelven un Set nuevo y no mutan los originales. Comprueba la compatibilidad del runtime que soporta tu proyecto.

MétodoResultado
union(other)valores de ambos conjuntos
intersection(other)valores presentes en ambos
difference(other)valores del primero que no están en el segundo
symmetricDifference(other)valores presentes solo en uno
isSubsetOf(other)si todos los valores están en el otro
isSupersetOf(other)si contiene todos los valores del otro
isDisjointFrom(other)si no comparten valores
const frontend = new Set(["js", "css", "html"])
const backend = new Set(["js", "sql", "http"])
;[...frontend.union(backend)]
// ['js', 'css', 'html', 'sql', 'http']
;[...frontend.intersection(backend)]
// ['js']
;[...frontend.difference(backend)]
// ['css', 'html']
frontend.isDisjointFrom(new Set(["python"])) // true

Si debes soportar un entorno sin estos métodos, combina filter y has:

const intersection = new Set(
[...frontend].filter((value) => backend.has(value))
)
;[...intersection] // ['js']

Las colecciones débiles no se pueden recorrer ni exponen size; así el programa no puede observar cuándo el recolector de basura libera una clave. Son apropiadas para asociar información a la vida útil de otro objeto.

const metadata = new WeakMap()
const button = document.querySelector("button")
if (button) {
metadata.set(button, { clicks: 0 })
metadata.get(button) // { clicks: 0 }
metadata.has(button) // true
}

No uses WeakMap como caché si necesitas listar, contar o invalidar todas sus entradas.

WeakMap.prototype.getOrInsert y getOrInsertComputed siguen la misma semántica que sus equivalentes de Map, pero la clave debe poder mantenerse débilmente y las entradas continúan sin ser enumerables.

WeakRef permite observar un objeto sin mantenerlo vivo. deref() devuelve el objeto o undefined si ya fue recolectado. FinalizationRegistry puede solicitar un callback después de la recolección.

let image = { url: "/cover.webp" }
const reference = new WeakRef(image)
reference.deref() // objeto mientras siga disponible
image = null
// En algún momento futuro podría devolver undefined.
// No existe una forma fiable de forzar o predecir ese momento.

No uses estas APIs para cerrar archivos, conexiones, locks ni recursos críticos: el recolector puede tardar o no ejecutar la finalización antes de terminar el proceso. Son herramientas especializadas para caches y observabilidad; WeakMap suele resolver el caso común con menos riesgo.

function countBy(values) {
const frequencies = new Map()
for (const value of values) {
frequencies.set(value, (frequencies.get(value) ?? 0) + 1)
}
return frequencies
}
const result = countBy(["error", "info", "error", "warning"])
;[...result]
// [['error', 2], ['info', 1], ['warning', 1]]