Menu

Map y Set en JavaScript: cuándo usarlos en vez de objetos y arrays

Cómo funcionan Map y Set en JavaScript, en qué se diferencian de los objetos y arrays clásicos, y cuándo conviene de verdad usarlos.

Dos colecciones más allá de Object y Array

Los objetos planos y los arrays cubren la mayoría de las necesidades en JavaScript, pero no están pensados para todos los casos. Map y Set son colecciones nativas que cubren dos huecos concretos: búsquedas por clave cuando las claves no son strings, y comprobaciones de pertenencia sin duplicados.

Están en el lenguaje desde ES2015. Ambas son iterables, ambas exponen la propiedad .size y ambas se llevan bien con el operador spread. La idea es sencilla:

  • Map — como un objeto, pero las claves pueden ser de cualquier tipo y se respeta el orden de inserción.
  • Set — como un array, pero sin valores repetidos y con búsqueda rápida.

Cómo crear y usar un Map en JavaScript

Un Map guarda pares clave/valor. Se crea con new Map() y se maneja con .set(), .get(), .has() y .delete():

index.js
Output
Click Run to see the output here.

También puedes pasarle al constructor un array de pares [clave, valor] para inicializarlo:

index.js
Output
Click Run to see the output here.

Esa forma de arreglo con dos elementos aparece por todos lados cuando trabajas con Maps: es la manera en que se representan las entradas al iterar.

Map vs objeto en JavaScript: ¿para qué molestarse?

Los objetos de toda la vida parecen hacer exactamente lo mismo. Y la verdad, casi siempre cumplen. Pero Map soluciona un par de detalles puntuales que con los objetos se vuelven incómodos:

index.js
Output
Click Run to see the output here.

Los objetos heredan de Object.prototype, así que claves como toString, constructor o hasOwnProperty ya existen en cualquier objeto desde el primer momento. Los Map, en cambio, no arrastran ese equipaje: las únicas claves que existen son las que tú añades.

Otras diferencias que conviene tener claras:

  • Cualquier tipo de clave. Un Map acepta objetos, funciones, números o booleanos como claves. Un objeto convierte en silencio las claves no string a string: obj[1] y obj["1"] apuntan a la misma posición.
  • Orden de inserción garantizado. Los Map se iteran en el orden en que insertaste las entradas. Los objetos casi siempre también, pero las claves que parecen numéricas se ordenan primero, y ahí está la trampa.
  • Tamaño integrado. map.size es O(1). Con un objeto tendrías que escribir Object.keys(obj).length, que reconstruye un array cada vez.
  • Pensado para entradas cambiantes. Los motores de JS optimizan los Map para añadir y borrar con frecuencia. Los objetos están optimizados para registros con forma estable.

Usa un objeto cuando modeles un registro con claves string conocidas ({ name, email, age }). Usa un Map cuando las claves sean dinámicas, no sean strings, o cuando vayas a añadir y quitar entradas a menudo.

Cómo iterar un Map en JavaScript

Los Map son iterables, así que for...of funciona directamente y desestructurar cada entrada resulta de lo más natural:

index.js
Output
Click Run to see the output here.

Si solo quieres las claves o solo los valores, tira de .keys() o .values(). Y si prefieres .forEach(), también lo tienes disponible:

index.js
Output
Click Run to see the output here.

Para convertir un Map de nuevo en un objeto plano o en un array, usa el operador spread:

index.js
Output
Click Run to see the output here.

Crear y usar un Set en JavaScript

Un Set almacena valores únicos. Si intentas añadir un valor que ya existe, simplemente no pasa nada:

index.js
Output
Click Run to see the output here.

La unicidad se decide con la misma regla de igualdad que ===, aunque con una peculiaridad: dentro de un Set, NaN se considera igual a sí mismo, a pesar de que NaN === NaN devuelva false en cualquier otro contexto.

Puedes pasarle un iterable al constructor para inicializar el Set, y de ahí sale el clásico truco para eliminar duplicados de un array en JavaScript:

index.js
Output
Click Run to see the output here.

Una sola línea, cualquier tipo primitivo. Con arrays de objetos esto no funciona —dos objetos distintos con los mismos campos siguen siendo dos valores diferentes—, pero para cadenas, números y booleanos es la forma idiomática de eliminar duplicados de un array en JavaScript.

Set vs Array: ¿cuándo cambiar?

Tanto los arrays como los Sets guardan una colección de valores, así que, ¿cuándo conviene uno u otro?

Tira de Set cuando:

  • Los valores deben ser únicos y quieres que sea el propio runtime quien lo garantice.
  • Haces muchas comprobaciones de pertenencia. set.has(x) es O(1); array.includes(x) es O(n). Dentro de un bucle, esa diferencia se nota muchísimo.
  • Solo te importa el orden de inserción. Los Sets iteran en orden de inserción, pero no permiten acceso por índice.

Quédate con un array cuando:

  • Necesites acceso por posición: arr[0], cortes (slicing), ordenación.
  • Los duplicados tengan sentido, como un carrito de compra con dos unidades del mismo producto.
  • Vayas a tirar mucho de métodos de array como .map, .filter o .reduce. Los Sets no los tienen; tendrías que volcarlos antes a un array con spread.

Un ejemplo rápido que deja ver el impacto en rendimiento:

index.js
Output
Click Run to see the output here.

Si banned fuera un array, cada callback de filter tendría que recorrer la lista entera. Usando un Set, cada búsqueda es de tiempo constante.

Cómo iterar un Set en JavaScript

Funciona igual que con un Map: for...of sirve sin más, y con el spread obtienes un array al instante:

index.js
Output
Click Run to see the output here.

Los Set también exponen .keys(), .values() y .entries() para mantener la simetría con Map, aunque en un Set las claves y los valores sean lo mismo. En la práctica, lo normal es iterar directamente sobre el set.

Ejemplo práctico: contar visitantes únicos por página

Combinando ambas estructuras: un Map que asocia rutas de páginas con un Set de IDs de visitantes:

index.js
Output
Click Run to see the output here.

El Map se encarga de asociar cada ruta con su bucket, mientras que el Set se ocupa de evitar duplicados dentro de cada uno. Podrías hacer lo mismo con un objeto plano y arrays, sí, pero acabarías llenando el código de comprobaciones con indexOf y hasOwnProperty por todos lados.

WeakMap y WeakSet en pocas palabras

Existen dos colecciones emparentadas que cubren un caso muy concreto: WeakMap y WeakSet. Guardan referencias de forma débil, lo que significa que cualquier entrada cuya clave (en WeakMap) o valor (en WeakSet) ya no esté referenciada en ningún otro sitio se recoge automáticamente por el recolector de basura.

index.js
Output
Click Run to see the output here.

Solo aceptan objetos como claves, no son iterables y tampoco tienen .size. Esto es intencional: si pudieras iterarlos, el garbage collector sería observable. Resultan útiles para cachear metadatos sobre objetos que no te pertenecen, aunque rara vez aparecen en el código del día a día.

Lo que viene: JSON

Map y Set funcionan de maravilla en memoria, pero ninguno sobrevive a JSON.stringify de una pieza: los Maps terminan como {} y los Sets también como {}. En la siguiente página toca JSON: cómo serializar y parsear datos, y qué patrones seguir para manejar las colecciones que vimos aquí cuando tienen que viajar por la red o escribirse en un archivo.

Preguntas frecuentes

¿Qué diferencia hay entre un Map y un Object en JavaScript?

Un Map acepta cualquier valor como clave —objetos, funciones, números, lo que sea—, mientras que un objeto convierte las claves a string (o símbolo). Además, Map lleva la cuenta de su tamaño con .size, itera en el orden de inserción y no hereda claves del prototipo, así que no te vas a chocar con toString o constructor. Tira de Map cuando las claves no sean strings o cuando vayas a añadir y quitar entradas constantemente.

¿Para qué sirve un Set en JavaScript?

Un Set guarda valores únicos: si metes un duplicado, lo ignora sin avisar. La forma más rápida de eliminar duplicados de un array es [...new Set(arr)]. Otra ventaja es que .has() es O(1), lo que le da mil vueltas a array.includes() cuando estás comprobando pertenencia dentro de un bucle.

¿Cómo se recorre un Map?

Con for...of directamente: for (const [key, value] of myMap) te desestructura cada entrada. También puedes iterar myMap.keys(), myMap.values() o myMap.entries(). El orden de iteración siempre coincide con el de inserción, algo que los objetos planos no garantizan cuando las claves parecen números.

Aprende a programar con Coddy

COMENZAR