"El buen diseño es obvio. El gran diseño es transparente"
- Joe Sparano

Diferencias entre Object y Map

5 minutos de lectura
Fecha: 26/7/2023

El Map es un tipo de dato relativamente nuevo en Javascript y por ello para algunos es desconocido, o incluso los que ya lo usan tampoco saben cuales son todas las diferencias por ejemplo con los objetos.

Y es que echando un vistazo rápido podría parecer que están un poco solapados y que sirven para prácticamente lo mismo, cosa que en parte es cierto porque son similares. Entonces ¿porque surgió la necesidad de crear los Map? si al fin y al cabo ya tenemos los objetos. Veamos que nos aportan.

Si quieres ver la documentación oficial de Mozilla, que es en la que me he basado para sacar esta información, la puedes consultar pulsando en este link.

Map no tiene keys por defecto

Los objetos tienen un prototype que puede tener ciertas keys que vienen establecidas por defecto sin que tu los sepas, por ejemplo cuando el objeto viene extendido de otro y ya traía métodos.

Seguridad

Precisamente por esto que mencionamos de que los objetos te pueden traer propiedades por defecto inyectadas a través del prototipo, podrían ocurrir ataques por inyección de objetos, tanto para el caso anterior como para este podríamos solucionarlo creándolos así Object.create(null) y nos aseguramos que no lo hayan mutado por el camino.

Con Map este problema no lo tendríamos.

Las keys admiten cualquier tipo de dato

En los objetos las keys solo pueden ser string o symbol (si quieres saber mas de symbol puedes ver este artículo que publique hace tiempo).

Sin embargo, en Map tu puedes poner como key cualquier tipo dato además de estos dos, por ejemplo en un map una key puede ser un number.

Map te asegura que el orden en el que metes las keys

Esto parece una tontería, pero realmente dependiendo de que navegador tengamos y en que versión este podría no devolver las keys ordenadas cuando iteráramos sobre ellas. En algunos casos vienen ordenadas en el orden en que fueron introducidas y en otros no.

Los maps garantizan que ese orden siempre se va a respetar y que nos podemos fiar, y eso es un punto a su favor porque en ocasiones este tema sera necesario en nuestro código.

Tamaño

El Map tiene una propiedad llamada size que nos devuelve el número de key/values que tiene, mientras que el objeto no tiene nada por el estilo.

En caso de querer hacer lo mismo con un objeto primero tendrías que hacer el Object.keys para que nos diera un array con las claves, y luego sobre el array usar la propiedad length que si que nos devolvería el número.

Iteraciones

Pasa algo parecido al tema del tamaño, map es iterable por defecto, cosa que los objetos no. Y como pasaba en el ejemplo anterior para iterar encima de un objeto tendríamos que valernos de métodos como Object.keys o Object.entries para que nos devuelva un array y luego podríamos usar el for…in

Para iterar con Map es mucho más sencillo, os muestro como, primero de todo los maps también tiene métodos para devolvernos las keys y los valores como en los objetos (En vez de Object.keys(miobjeto) tengo mimap.keys() y en vez de Object.entries(miobjeto) tengo mimap.values()), de manera que podría iterar sobre ellos de la misma manera, por mencionar una diferencia en los objetos devuelven Arrays y en los Maps son iteradores, pero en la práctica podemos hacer lo mismo.

const myMap = new Map();
  myMap.set(0, "zero");
  myMap.set(1, "one");

  // Con keys
  for (const key of myMap.keys()) {
    console.log(key);
  }

  // Con values
  for (const value of myMap.values()) {
    console.log(value);
  }

O podría directamente desestructurarlo para conseguir lo mismo

const myMap = new Map();
  myMap.set(0, "zero");
  myMap.set(1, "one");

  // Forma abreviada
  for (const [key, value] of myMap) {
    console.log(`${key} = ${value}`);
  }

  // Con entries (cuidado no confundir con el entries de los objetos, este devuelve el par clave/valor y no solo el valor)
  for (const [key, value] of myMap.entries()) {
    console.log(`${key} = ${value}`);
  }

Pero ahora llega lo mejor, y es que ser iterables por defecto permite que también podamos hacer esto

const myMap = new Map();
  myMap.set(0, "zero");
  myMap.set(1, "one");

  // Con forEach
  myMap.forEach((value, key) => {
    console.log(`${key} = ${value}`);
  });

Y no solo recorrerlos sino que por ser iterables puedes crear Maps a partir de Arrays y viceversa

const array1 = [
    ["key1", "value1"],
    ["key2", "value2"],
  ];

  // Crear un Map a partir de un Array
  const myMap = new Map(array1);

  // Crear un Array a partir de un Map con from
  const myArray = Array.from(myMap);

  // Crear un Array a partir de un Map con spread operator
  const myArray = [...myMap];

Rendimiento

Este punto me parece importantísimo y una de las claves para decantarnos por uno o por otro, el rendimiento de los objetos es tremendamente superior a la hora de modificar alguna de sus claves, sin embargo los Maps son muchísimo más rápidos tanto para añadir nuevas como para borrar las ya existentes.

Voy a hacer una demostración de la diferencia que hay en esto que os digo, por ejemplo veamos cuanto tiempo tarda un Map en escribir 1000 nuevas entradas en comparación con un objeto.

const iterations = 1000;

  // Con MAP
  const map = new Map();

  for(let i = 0; i < iterations; i++) {
    map.set(`property${i}`, "Hello world");
  }

  // Con Object
  const obj = {};

  for(let i = 0; i < iterations; i++) {
    obj[`property${i}`] = "Hello world";
  }

Map vs object introduciendo entradas

Para cuando el map ya completó las 1000 escrituras el objeto solo lleva 620, esto tiene cierta variabilidad cada vez que le das, también en función del navegador, pero en por las pruebas parece que Map es entre un 30 - 40% más rápido.

Ahora veamos lo mismo pero borrando al final la propiedad que creas…

const iterations = 1000;

  // Con MAP
  const map = new Map();

  for(let i = 0; i < iterations; i++) {
    map.set(`property${i}`, "Hello world");
    map.delete(`property${i}`);
  }

  // Con Object
  const obj = {};

  for(let i = 0; i < iterations; i++) {
    obj[`property${i}`] = "Hello world";
    delete obj[`property${i}`];
  }

Map vs object borrando entradas

Aquí me da entre un 25-30% mas velocidad con Map, un poquito menos que en el caso anterior, pero sigue siendo significativamente mejor el rendimiento. Llegados a este punto podríamos pensar que Map en general tiene un rendimiento muy superior a los Objetos, pero nada más lejos de la realidad, veamos el último caso.

Lo que voy ahora es a actualizar 1000 veces la misma propiedad con ambos tipos de dato.

const iterations = 1000;

  // Con MAP
  const map = new Map();
  map.set('property', 0);
  for(let i = 0; i < iterations; i++) {
    map.set('property', i);
  }

  // Con Object
  const obj = {
    property: 0
  };

  for(let i = 0; i < iterations; i++) {
    obj.property = i;
  }

Map vs object actualizando entradas

Y esto si que es una autentica salvajada, mientras que el objeto ya ha actualizado mil veces la propiedad, Map solo lleva 50. El Map solo tiene en torno a un 5% de la velocidad que tiene un objeto.

Esto sucede por lo siguiente, cuando creamos un objeto JavaScript lo entiende como una estructura monomórfica a efectos prácticos, es decir que una vez declarado espera que no cambie la estructura. Puede cambiar los valores de sus propiedades a velocidades increíbles como acabamos de ver en el tercer ejemplo, pero cuando le pides que cambie su estructura, es decir, que añada o elimine alguna de sus propiedades, ahi es cuando se vuelve mas lento.

En resumen para estructuras que no van a cambiar los objetos tienen mejor rendimiento, pero para las que si lo hacen los Maps son mas rápidos.

Serialización y parseo

Map no tiene manera métodos de serialización y parseo como si tienen los objetos con JSON.stringify y JSON.parse, aunque si que podemos modificar estos y construirnos los nuestros propios para que nos valgan con Map.

Es interesante que en la documentación de Mozilla ya dejan un enlace para hacer esto y que podéis consultar aquí.

Os dejo el código porque es interesante

function replacer(key, value) {
  if(value instanceof Map) {
    return {
      dataType: 'Map',
      value: Array.from(value.entries()), // or with spread: value: [...value]
    };
  } else {
    return value;
  }
}

function reviver(key, value) {
  if(typeof value === 'object' && value !== null) {
    if (value.dataType === 'Map') {
      return new Map(value.value);
    }
  }
  return value;
}

const originalValue = new Map([['a', 1]]);
const str = JSON.stringify(originalValue, replacer);  // {"dataType":"Map","value":[["a",1]]}
const newValue = JSON.parse(str, reviver); // Map(1) {'a' => 1}

Y con esto quedarían explicadas las diferencias, dependiendo de cada caso como hemos visto serán más apropiados los Objetos o los Maps, para mi la clave están en el rendimiento y en la velocidad de desarrollo (los métodos y propiedades que tengan) pero como hemos visto hay mas puntos a considerar y cada uno decidirá que le aporta más.