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():
También puedes pasarle al constructor un array de pares [clave, valor] para inicializarlo:
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:
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
Mapacepta objetos, funciones, números o booleanos como claves. Un objeto convierte en silencio las claves no string a string:obj[1]yobj["1"]apuntan a la misma posición. - Orden de inserción garantizado. Los
Mapse 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.sizees O(1). Con un objeto tendrías que escribirObject.keys(obj).length, que reconstruye un array cada vez. - Pensado para entradas cambiantes. Los motores de JS optimizan los
Mappara 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:
Si solo quieres las claves o solo los valores, tira de .keys() o .values(). Y si prefieres .forEach(), también lo tienes disponible:
Para convertir un Map de nuevo en un objeto plano o en un array, usa el operador spread:
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:
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:
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,.filtero.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:
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:
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:
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.
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.