¿Qué es un Map?

Mapa: Estructura de datos de pares clave-valor


Los Map en Javascript son estructuras de datos nativas que permiten implementar una estructura de tipo mapa, es decir, una estructuras donde tiene valores guardados a través de una clave para identificarlos. Comúnmente, esto se denomina pares clave-valor.

const map = new Map();                                        // Map({}) (Mapa vacío)
const map = new Map([[1, "uno"]]);                            // Map({ 1=>"uno" })
const map = new Map([[1, "uno"], [2, "dos"], [3, "tres"]]);   // Map({ 1=>"uno", 2=>"dos", 3=>"tres" })

map.constructor.name;                     // "Map"

En este ejemplo, creamos un elemento map, que no es más que un mapa de pares clave-valor. El primer map se define como un mapa vacío, el segundo, es un mapa con un solo elemento, y el tercero con 3 elementos. Para inicializar los mapas con datos, se introduce como parámetro un array de entradas (un array de arrays), que en nuestro tercer caso tiene estas combinaciones:

  • Clave: 1 => Valor: "uno"
  • Clave: 2 => Valor: "dos"
  • Clave: 3 => Valor: "tres"

Por lo tanto, si consultamos map con la clave 2, nos devolverá un "dos".

¿Qué son los Map?

Los tipos de dato Map son muy similares a los Objetos de Javascript, ya que estos últimos se pueden usar como estructuras de diccionario mediante pares clave-valor. Sin embargo, los Object tienen algunas diferencias como que pueden colisionar algunos nombres de claves o que las claves deben ser o , entre varias otras.

Una estructura de tipo Map tiene las siguientes propiedades o métodos:

Propiedad o Método Descripción
.size Propiedad que devuelve el número de elementos que tiene el mapa.
.set(key, value) Establece o modifica la clave key con el valor value. Muta
.has(key) Comprueba si key ya existe en el mapa y devuelve si existe o no.
.get(key) Obtiene el valor de la clave key del mapa.
.delete(key) Elimina el elemento con la clave key del mapa. Devuelve si lo eliminó correctamente.
.clear() Vacía el mapa completamente.

Vamos a analizar los diferentes métodos y propiedades que tienen los mapas.

Propiedad size

Si quieres saber cuántos elementos tiene un mapa, puedes utilizar la propiedad .size, que funciona de forma muy similar al .length de los array, por ejemplo.

const map = new Map();
map.size;    // 0

const map = new Map([[1, "uno"], [2, "dos"]]);
map.size;    // 2

const map = new Map([[1, "uno"], [2, "dos"], [1, "tres"]]);
map.size;    // 2 (El 1->"tres" sobreescribe al anterior)

Observa que si introducimos un nuevo par clave-valor que tiene la misma clave que otro (tenemos dos que comparten la clave 1), se sobreescribirá. No pueden existir dos pares clave-valor con la misma clave.

Métodos

Veamos ahora los diferentes métodos que tienen las estructuras de conjuntos Map.

Establecer elementos (set)

El método .set() fija un par clave-valor en el mapa. Observa que hay un pequeño matiz muy importante de diferencia entre el concepto «añadir» (.add()) y el concepto «establecer» o «fijar» (.set()):

  • Si usamos .set() para una clave que no existe, se añade al mapa.
  • Si usamos .set() para una clave que ya existe, la sobreescribe.
const map = new Map();

map.set(5, "cinco");
map.set("A", "letra A");
map.set(5, "cinco sobreescrito");   // Sobreescribe el anterior

map;            // Map({ 5=>"cinco sobreescrito", "A"=>"letra A" })

Ten en cuenta que al contrario que los , los pueden utilizar como clave cualquier tipo de dato. En el caso de los debes utilizar un o un .

Comprobar si existen (has)

Para comprobar si un elemento existe en un mapa, se debe hacer a través de su clave, y se utiliza el método .has().Este método devuelve un , por lo que si existe la clave, nos devolverá true, y en caso contrario, nos devolverá false.

const map = new Map([[1, "uno"], [2, "dos"], [3, "tres"]]);

map.has(2);     // true
map.has(34);    // false
map.set(34, "treinta y cuatro");
map.has(34);    // true

Recuerda que si estás utilizando tipos de datos más complejos como o , deberías tenerlos almacenados en una variable, ya que si los creas al momento de pasarlos por parámetro, estarás pasando su referencia, y podrían no ser los mismos objetos aunque los escribas exactamente igual.

Borrar elementos (delete)

Si necesitamos borrar algún elemento del mapa, lo podemos hacer mediante el método .delete(). Devuelve un a true si lo consigue eliminar, en caso contrario, devolverá false.

const map = new Map([[1, "uno"], [2, "dos"], [3, "tres"]]);

map.delete(3);    // true
map.delete(39);   // false

map;              // Map({ 1=>"uno", 2=>"dos" })

Vacíar conjunto (clear)

Por último, utilizando el método .clear() borraremos todos los elementos del mapa, dejándolo vacío. Este método no devuelve nada.

const map = new Map([[1, "uno"], [2, "dos"], [3, "tres"]]);

map.clear();

map.size;         // 0

Convertir a Arrays

Si tenemos claro el proceso de desestructuración, podemos convertir los Map en o incluso en de forma muy sencilla. Eso sí, antes te recomiendo mirarte el artículo de Iteradores en Objetos:

const map = new Map([[1, "uno"], [2, "dos"], [3, "tres"]]);

map.size;                   // 3 (Contiene 3 elementos)
map.constructor.name;       // "Map"
const entries = [...structuredClone(map)];

entries.constructor.name;   // "Array"
entries;                    // [[1, "uno"], [2, "dos"], [3, "tres"]]

Recuerda utilizar structuredClone() para clonar la estructura si tiene elementos anidados, ya que sino sólo realizará una copia superficial y utilizará referencias para los elementos anidados.

Este array de entradas que nos da como resultado, lo podríamos utilizar para crear un nuevo map, o incluso un objeto:

const map = new Map(entries);
map;      // Map({ 1=>"uno", 2=>"dos", 3=>"tres" })

const object = Object.fromEntries(entries);
object;   // { 1: "uno", 2: "dos", 3: "tres" }

Aún así, recuerda que habría ciertos Map que quizás podrían dar conflictos al pasar a objeto, como por ejemplo, si tienes una clave toString. Más adelante comentaremos las diferencias.

¿Qué son los WeakMap?

Al igual que ocurre con los Set y los WeakSet, con los Map tenemos una estructura denominada WeakMap. La idea es la misma: se trata de una estructura derivada, muy similar a los Map, pero con algunas diferencias.

Diferencias con los Map

Al margen de algunas diferencias que detallaremos más adelante, la diferencia principal de los Map con los WeakMap es que estos últimos, no permiten utilizar tipos primitivos (, , ) como clave, mientras que el Map si lo permite:

// *** Map
const map = new Map([[1, "uno"]]);                            // OK
const map = new Map([[{ id: 1, type: "number" }, "uno"]]);    // OK

// *** WeakMap
const map = new WeakMap([[1, "uno"]]);
// ERROR: Uncaught TypeError: Invalid value used in weak map key

const map = new WeakMap([[{ id: 1, type: "number" }, "uno"]]); // OK

La razón de utilizar WeakMap en lugar de Map, es porque los primeros utilizan referencias débiles a un objeto, o lo que es lo mismo, si ese objeto no se utiliza (no está referenciado) en ninguna otra parte del código, se eliminará del WeakMap automáticamente y en cuanto el Garbage Collector (Recolector de basura) lo decida, lo borrará de memoria.

Tabla de resumen de diferencias

A continuación, una tabla resumen de las diferencias entre Map, WeakMap y Object:

Característica Map WeakMap Object
Se pueden insertar claves repetidas
Se pueden insertar claves con tipos primitivos Sólo o
Si no se usa el elemento, se elimina del map
Se puede convertir a array (es iterable) Object.entries(obj)
Pueden colisionar algunas claves *
Las claves garantizan un orden por inserción
Propiedad .size Object.keys(obj).length
Método .set() ❌ Se usa asignación por clave
Método .get() ❌ Se usa acceso a la clave
Método .has() Object.keys(obj).includes(key)
Método .delete()
Método .clear()

Respecto a lo que se menciona de que pueden colisionar algunas claves, es debido a que los comparten claves y propiedades del objeto, ya que se trata de una estructura de datos que mezcla propiedades, métodos y los datos del elemento. Sin embargo, en el caso de los maps, se separa la API de la estructura de los datos almacenados.

Observa el siguiente ejemplo:

const object = {};
object.toString = 42;
object.toString();     // ERROR: obj.toString is not a function

const map = new Map([["toString", 4]]);
map.toString();        // OK: "[object Map]"

Aquí se puede ver, como almacenando una propiedad llamada toString en el objeto, colisiona con el método ya existente .toString(), sobreescribiéndolo y dando problemas a la hora de convertir el objeto a . Sin embargo, con los no hay problema.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev