Saltar al contenido
set-y-map.js · devschool

Set y Map en JavaScript

Lección 18 de 38 · 9 min de lectura · Actualizado el

En esta lección
  1. Set: una colección sin repetidos
  2. Map: un diccionario de verdad
  3. Map frente a objeto
  4. Agrupar con Object.groupBy y Map.groupBy
  5. Errores frecuentes
  6. Resumen

Los arrays y los objetos resuelven casi todo, pero hay dos problemas en los que se quedan cortos: guardar valores sin repetir y asociar datos a claves que no son texto. Para eso JavaScript tiene dos estructuras de datos específicas: Set (un conjunto de valores únicos) y Map (un diccionario clave → valor de verdad). En esta lección verás cómo usarlas, cuándo convienen más que un array o un objeto y cómo agrupar datos con Object.groupBy.

Antes conviene que domines los arrays, los objetos y el operador spread de desestructuración y spread.

Set: una colección sin repetidos

Un Set guarda valores únicos. Si añades uno que ya está, simplemente no pasa nada:

const etiquetas = new Set();
etiquetas.add("js");
etiquetas.add("css");
etiquetas.add("js"); // ya estaba: se ignora

console.log(etiquetas);      // Set(2) { 'js', 'css' }
console.log(etiquetas.size); // 2

Fíjate en que el tamaño es size, no length. Puedes crearlo ya con datos pasando un array (o cualquier cosa recorrible), y add se puede encadenar:

const letras = new Set(["a", "b"]).add("c").add("a");
console.log(letras); // Set(3) { 'a', 'b', 'c' }

Comprobar, borrar y vaciar

etiquetas.has("css");     // true
etiquetas.has("html");    // false

etiquetas.delete("css");  // true: estaba y lo borra
etiquetas.delete("java"); // false: no estaba

etiquetas.clear();        // lo vacía entero

¿Por qué no usar array.includes? Porque has es mucho más rápido con muchos datos: el Set está organizado internamente para encontrar un valor sin recorrerlos todos. Con 10 elementos da igual; con 100 000, se nota.

Recorrer un Set

Un Set mantiene el orden de inserción y se recorre con for...of o forEach:

for (const letra of letras) {
  console.log(letra); // a, b, c
}

Lo que no tiene es acceso por posición: letras[0] es undefined. Si lo necesitas, conviértelo en array.

Quitar duplicados de un array

Es el uso más frecuente, y cabe en una línea:

const visitas = ["ana", "luis", "ana", "eva", "luis", "ana"];

const unicos = [...new Set(visitas)];
console.log(unicos);                 // [ 'ana', 'luis', 'eva' ]
console.log(new Set(visitas).size);  // 3 usuarios distintos

new Set(visitas) elimina los repetidos y [...] lo vuelve a convertir en array. También vale Array.from(new Set(visitas)).

Cómo decide que dos valores son iguales

Compara como === (salvo que considera NaN igual a NaN). Así que 1 y "1" son distintos, y dos objetos con el mismo contenido también:

new Set([1, "1", 1]).size;          // 2
new Set([{ id: 1 }, { id: 1 }]).size; // 2: son dos objetos diferentes

Para quitar objetos repetidos por un campo, usa un Map con ese campo como clave, o un Set de ids.

Operaciones de conjuntos

Intersección, unión y diferencia se pueden hacer con spread y filter:

const a = new Set(["js", "css", "html"]);
const b = new Set(["js", "python"]);

const comunes = new Set([...a].filter((x) => b.has(x))); // Set(1) { 'js' }

Los navegadores y Node recientes ya incluyen métodos propios: a.intersection(b), a.union(b), a.difference(b), a.isSubsetOf(b)… Son de ES2025, así que comprueba que tu entorno los soporta antes de usarlos.

Map: un diccionario de verdad

Un Map guarda pares clave → valor, como un objeto, pero pensado precisamente para eso:

const stock = new Map();
stock.set("teclado", 12);
stock.set("raton", 0).set("taza", 30); // set también se encadena

stock.get("taza");    // 30
stock.get("silla");   // undefined
stock.has("raton");   // true
stock.size;           // 3

stock.set("taza", 29); // si la clave existe, sustituye el valor
stock.delete("raton");
console.log(stock); // Map(2) { 'teclado' => 12, 'taza' => 29 }

Se puede crear con datos pasando un array de pares [clave, valor]:

const precios = new Map([
  ["cafe", 1.5],
  ["te", 1.2],
]);

Claves de cualquier tipo

Esta es la gran diferencia con un objeto. En un objeto, las claves se convierten a texto. En un Map, una clave puede ser un número, un booleano, un objeto o incluso una función:

const ana = { nombre: "Ana" };
const luis = { nombre: "Luis" };

const puntos = new Map();
puntos.set(ana, 120);
puntos.set(luis, 80);
puntos.get(ana); // 120

Con un objeto normal, las dos claves se convertirían en el texto "[object Object]" y la segunda pisaría a la primera:

const mal = {};
mal[ana] = 120;
mal[luis] = 80;
console.log(mal); // { '[object Object]': 80 }

Y 1 y "1" son claves distintas en un Map, pero la misma en un objeto.

Recorrer un Map

Un Map se recorre en orden de inserción, y cada vuelta te da un par que puedes desestructurar:

for (const [producto, precio] of precios) {
  console.log(`${producto}: ${precio} €`);
}
// cafe: 1.5 €
// te: 1.2 €

[...precios.keys()];    // [ 'cafe', 'te' ]
[...precios.values()];  // [ 1.5, 1.2 ]
[...precios.entries()]; // [ [ 'cafe', 1.5 ], [ 'te', 1.2 ] ]

precios.forEach((valor, clave) => console.log(clave, valor));

Cuidado con forEach: recibe primero el valor y después la clave.

Un caso real: contar

Contar cuántas veces aparece cada palabra es un uso típico:

const palabras = "el gato y el perro y el loro".split(" ");
const frecuencia = new Map();

for (const palabra of palabras) {
  frecuencia.set(palabra, (frecuencia.get(palabra) ?? 0) + 1);
}
console.log(frecuencia);
// Map(5) { 'el' => 3, 'gato' => 1, 'y' => 2, 'perro' => 1, 'loro' => 1 }

Convertir entre objeto y Map

Object.entries convierte un objeto en un array de pares, justo lo que acepta new Map. Object.fromEntries hace el camino inverso:

const obj = { cafe: 1.5, te: 1.2 };

const mapa = new Map(Object.entries(obj)); // Map(2) { 'cafe' => 1.5, 'te' => 1.2 }
const deVuelta = Object.fromEntries(mapa); // { cafe: 1.5, te: 1.2 }

Object.fromEntries también sirve para transformar un objeto sin Map de por medio:

const rebajado = Object.fromEntries(
  Object.entries(obj).map(([producto, precio]) => [producto, precio * 0.9])
);
// { cafe: 1.35, te: 1.08 }

Cuidado: JSON.stringify no sabe convertir un Map: devuelve "{}". Para guardarlo o enviarlo, conviértelo antes con Object.fromEntries(mapa) (si las claves son texto) o con [...mapa].

Map frente a objeto

Objeto {}Map
ClavesSolo texto (y símbolos)Cualquier tipo
TamañoObject.keys(o).lengthmapa.size
RecorrerObject.entries(o)for...of directo
OrdenCasi siempre el de inserción (los números van primero)Siempre el de inserción
Claves heredadasSí: "toString" in {} es trueNo, empieza vacío
JSONDirectoHay que convertirlo
Añadir y borrar a menudoMás lentoOptimizado

Usa un objeto para representar una “cosa” con propiedades fijas y conocidas (un usuario, un producto, una configuración) y para datos que vas a convertir a JSON.

Usa un Map cuando es una colección de pares que crece y cambia, cuando las claves vienen de datos (ids, palabras, nombres de usuario) o cuando las claves no son texto.

Nota: existen también WeakMap y WeakSet. Solo admiten objetos como claves y no impiden que el recolector de basura los elimine cuando ya nadie más los usa. No se pueden recorrer ni tienen size. Sirven para asociar datos extra a objetos que no controlas (por ejemplo, elementos del DOM) sin provocar fugas de memoria. Al empezar, rara vez los necesitarás.

Agrupar con Object.groupBy y Map.groupBy

Agrupar una lista por un criterio es tan habitual que desde ES2024 existe un método para ello. Recibe el array y una función que devuelve la clave del grupo de cada elemento:

const productos = [
  { nombre: "Teclado", categoria: "informatica", precio: 45 },
  { nombre: "Taza", categoria: "hogar", precio: 8 },
  { nombre: "Ratón", categoria: "informatica", precio: 20 },
];

const porCategoria = Object.groupBy(productos, (p) => p.categoria);
// {
//   informatica: [ { nombre: 'Teclado', ... }, { nombre: 'Ratón', ... } ],
//   hogar: [ { nombre: 'Taza', ... } ]
// }

La clave puede ser cualquier cálculo:

Object.groupBy(productos, (p) => (p.precio >= 20 ? "caros" : "baratos"));
// { caros: [ Teclado, Ratón ], baratos: [ Taza ] }

Map.groupBy hace lo mismo pero devuelve un Map, útil si las claves no son texto:

const grupos = Map.groupBy(productos, (p) => p.categoria);
grupos.get("hogar"); // [ { nombre: 'Taza', categoria: 'hogar', precio: 8 } ]

Antes había que hacerlo con reduce, como viste en métodos de arrays. Si aparece [Object: null prototype] al mostrar el resultado en Node, es normal: el objeto se crea sin propiedades heredadas.

Errores frecuentes

  • Usar length en vez de size: set.length y map.length son undefined.
  • Leer un Map con corchetes: mapa["cafe"] no funciona; usa mapa.get("cafe"). Y mapa.cafe = 2 crea una propiedad normal, no una entrada del Map.
  • Esperar que el Set elimine objetos “iguales”: compara referencias, no contenido.
  • Guardar un Map con JSON.stringify directamente: obtienes {}.
  • Buscar con objetos distintos: puntos.get({ nombre: "Ana" }) da undefined aunque el contenido coincida; tiene que ser el mismo objeto.
  • Confundir el orden en forEach de un Map: primero valor, luego clave.

Resumen

SetMap
new Set(array)new Map([[clave, valor], ...])
add(valor)set(clave, valor)
has(valor)has(clave) / get(clave)
delete(valor)delete(clave)
size, clear()size, clear()
for (const v of set)for (const [k, v] of map)
[...new Set(arr)] quita duplicadosObject.entries / Object.fromEntries para convertir
  • Object.groupBy(lista, fn) agrupa en un objeto; Map.groupBy en un Map.
  • Objeto para “cosas” con propiedades fijas; Map para colecciones de pares que cambian.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

set-y-map.js
Consola

Pon a prueba lo que has aprendido

[JavaScript] ¿Qué muestra la consola?
const s = new Set([3, 1, 3, 2, 1]);
console.log(s.size, [...s]);

[JavaScript] ¿Qué muestra la consola?
const m = new Map();
m.set(1, "uno");
m.set("1", "texto");
console.log(m.size, m.get(1));

[JavaScript] ¿Cuál es la forma más corta de quitar los repetidos de un array lista?

[JavaScript] ¿Qué devuelve JSON.stringify(new Map([["a", 1]]))?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.