Mapa vs Objeto en JavaScript

290

Acabo de descubrir chromestatus.com y, después de perder varias horas de mi día, encontré esta entrada de características :

Mapa: Los objetos de mapa son mapas simples de clave / valor.

Eso me confundio. Los objetos JavaScript normales son diccionarios, entonces, ¿en qué se Mapdiferencia de un diccionario? Conceptualmente, son idénticos (de acuerdo con ¿Cuál es la diferencia entre un Mapa y un Diccionario? )

Las referencias de documentación chromestatus tampoco ayudan:

Los objetos de mapa son colecciones de pares clave / valor donde las claves y los valores pueden ser valores de lenguaje ECMAScript arbitrarios. Un valor clave distinto solo puede aparecer en un par clave / valor dentro de la colección del Mapa. Distintos valores clave como discriminados utilizando el algoritmo de comparación que se selecciona cuando se crea el Mapa.

Un objeto Map puede iterar sus elementos en orden de inserción. El objeto de mapa debe implementarse utilizando tablas hash u otros mecanismos que, en promedio, proporcionan tiempos de acceso que son sublineales en la cantidad de elementos en la colección. Las estructuras de datos utilizadas en esta especificación de objetos de Mapa solo tienen la intención de describir la semántica observable requerida de los objetos de Mapa. No pretende ser un modelo de implementación viable.

... todavía me suena como un objeto, así que claramente me he perdido algo.

¿Por qué JavaScript está ganando un objeto (bien soportado) Map? ¿Qué hace?

Dave
fuente

Respuestas:

286

De acuerdo con Mozilla:

Un objeto Map puede iterar sus elementos en orden de inserción: un bucle for..of devolverá una matriz de [clave, valor] para cada iteración.

y

Los objetos son similares a los Mapas, ya que ambos le permiten establecer claves en valores, recuperar esos valores, eliminar claves y detectar si algo está almacenado en una clave. Debido a esto, los objetos se han utilizado como mapas históricamente; sin embargo, existen diferencias importantes entre Objetos y Mapas que hacen que usar un Mapa sea mejor.

Un objeto tiene un prototipo, por lo que hay claves predeterminadas en el mapa. Sin embargo, esto se puede omitir usando map = Object.create (nulo). Las claves de un objeto son cadenas, donde pueden ser cualquier valor para un mapa. Puede obtener el tamaño de un mapa fácilmente mientras tiene que realizar un seguimiento manual del tamaño de un objeto.

Use mapas sobre objetos cuando las claves son desconocidas hasta el tiempo de ejecución, y cuando todas las claves son del mismo tipo y todos los valores son del mismo tipo.

Use objetos cuando haya lógica que opere en elementos individuales.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

La iterabilidad en orden es una característica que los desarrolladores siempre han deseado, en parte porque garantiza el mismo rendimiento en todos los navegadores. Entonces para mí eso es grande.

El myMap.has(key)método será especialmente útil, y también la myMap.sizepropiedad.


fuente
13
Una desventaja, presumiblemente, es que un Mapa requiere más memoria (sin embargo, dentro del mismo orden de magnitud) para mantener el orden de inserción.
John Kurlak
44
Los mapas tienen otras características además del orden que se han mencionado aquí (usando cualquier objeto como clave, separación de claves y accesorios, etc.), pero FWIW en algunos casos define el orden de iteración de las propiedades del objeto plano por ES2015. Ver stackoverflow.com/a/32149345 .
JMM
2
No entiendo el significado, cuando dices, Un objeto tiene un prototipo, por lo que hay claves predeterminadas en el mapa. Sin embargo, esto se puede evitar usandomap = Object.create(null) . ¿Qué son las claves predeterminadas? ¿Cómo se relacionan las claves Object.prototype?
intercambio excesivo el
44
Mis pruebas en Chrome mostraron que los mapas no usan una cantidad significativa de memoria para mantener el orden. Creo que hubo 0.1 KB más por un millón de claves y no creo que eso fuera para mantener el orden. Sin embargo, ese ~ 0.1 KB parece ser una sobrecarga constante. Si creas un millón de mapas con una clave y lo comparas, es mucho más grande que el objeto.
jgmjgm
2
@luxon estás creando un objeto allí. La especificación ES6 requiere que el newoperador se use con el Mapsímbolo, es decir new Map, para crear un objeto de mapa. var a = {}es la abreviatura de (significado equivalente a)var a = Object.create(Object.prototype)
dudewad
104

La diferencia clave es que los Objetos solo admiten claves de cadena, mientras que los Mapas admiten más o menos cualquier tipo de clave.

Si lo hago obj[123] = truey luego Object.keys(obj)obtendré en ["123"]lugar de [123]. Un mapa preservaría el tipo de clave y devolvería, lo [123]cual es genial. Los mapas también le permiten usar objetos como claves. Tradicionalmente para hacer esto, tendrías que dar a los objetos algún tipo de identificador único para hacerlos hash (no creo que haya visto algo así como getObjectIden JS como parte del estándar). Los mapas también garantizan la preservación del orden, por lo que son mucho mejores para la preservación y, a veces, pueden ahorrarle la necesidad de hacer algunos tipos.

Entre mapas y objetos en la práctica hay varios pros y contras. Los objetos obtienen ventajas y desventajas al estar muy estrechamente integrados en el núcleo de JavaScript, lo que los distingue de un mapa significativamente más allá de la diferencia en el soporte clave.

Una ventaja inmediata es que tiene soporte sintáctico para los objetos, lo que facilita el acceso a los elementos. También tiene soporte directo para ello con JSON. Cuando se usa como hash, es molesto obtener un objeto sin ninguna propiedad. De forma predeterminada, si desea utilizar objetos como una tabla hash, se contaminarán y, a menudo, tendrá que recurrir hasOwnPropertya ellos al acceder a las propiedades. Puede ver aquí cómo, de forma predeterminada, los objetos están contaminados y cómo crear objetos con suerte no contaminados para usar como hashes:

({}).toString
    toString() { [native code] }
JSON.parse('{}').toString
    toString() { [native code] }
(Object.create(null)).toString
    undefined
JSON.parse('{}', (k,v) => (typeof v === 'object' && Object.setPrototypeOf(v, null) ,v)).toString
    undefined

La contaminación en los objetos no es solo algo que hace que el código sea más molesto, más lento, etc., sino que también puede tener consecuencias potenciales para la seguridad.

Los objetos no son tablas hash puras, pero están tratando de hacer más. Tiene dolores de cabeza como hasOwnPropertyno poder obtener la longitud fácilmente ( Object.keys(obj).length) y así sucesivamente. Los objetos no están destinados a ser utilizados únicamente como mapas hash, sino también como objetos dinámicos extensibles, por lo que cuando los usa como tablas hash puras surgen problemas.

Comparación / Lista de varias operaciones comunes:

    Object:
       var o = {};
       var o = Object.create(null);
       o.key = 1;
       o.key += 10;
       for(let k in o) o[k]++;
       var sum = 0;
       for(let v of Object.values(m)) sum += v;
       if('key' in o);
       if(o.hasOwnProperty('key'));
       delete(o.key);
       Object.keys(o).length
    Map:
       var m = new Map();
       m.set('key', 1);
       m.set('key', m.get('key') + 10);
       m.foreach((k, v) => m.set(k, m.get(k) + 1));
       for(let k of m.keys()) m.set(k, m.get(k) + 1);
       var sum = 0;
       for(let v of m.values()) sum += v;
       if(m.has('key'));
       m.delete('key');
       m.size();

Hay algunas otras opciones, enfoques, metodologías, etc., con diferentes altibajos (rendimiento, conciso, portátil, ampliable, etc.). Los objetos son un poco extraños al ser el núcleo del lenguaje, por lo que tiene muchos métodos estáticos para trabajar con ellos.

Además de la ventaja de que Maps preserva los tipos de clave y es capaz de admitir cosas como objetos como claves, están aislados de los efectos secundarios que tienen mucho los objetos. Un mapa es un hash puro, no hay confusión acerca de tratar de ser un objeto al mismo tiempo. Los mapas también se pueden ampliar fácilmente con funciones proxy. Los objetos actualmente tienen una clase de Proxy, sin embargo, el rendimiento y el uso de la memoria son sombríos, de hecho, crear su propio proxy que se parece a Map for Objects actualmente funciona mejor que Proxy.

Una desventaja sustancial para Maps es que no son compatibles con JSON directamente. El análisis es posible pero tiene varias interrupciones:

JSON.parse(str, (k,v) => {
    if(typeof v !== 'object') return v;
    let m = new Map();
    for(k in v) m.set(k, v[k]);
    return m;
});

Lo anterior introducirá un golpe de rendimiento serio y tampoco admitirá ninguna tecla de cadena. La codificación JSON es aún más difícil y problemática (este es uno de los muchos enfoques):

// An alternative to this it to use a replacer in JSON.stringify.
Map.prototype.toJSON = function() {
    return JSON.stringify({
        keys: Array.from(this.keys()),
        values: Array.from(this.values())
    });
};

Esto no es tan malo si solo está usando Maps, pero tendrá problemas cuando mezcle tipos o use valores no escalares como claves (no es que JSON sea perfecto con ese tipo de problema, la referencia de objeto circular de IE). No lo he probado, pero es probable que perjudique gravemente el rendimiento en comparación con stringify.

Otros lenguajes de secuencias de comandos a menudo no tienen problemas, ya que tienen tipos explícitos no escalares para Map, Object y Array. El desarrollo web a menudo es un problema con los tipos no escalares en los que tiene que lidiar con cosas como PHP combina Array / Map con Object usando A / M para propiedades y JS combina Map / Object con Array extendiendo M / O. Fusionar tipos complejos es la maldición del diablo de los lenguajes de scripting de alto nivel.

Hasta ahora, estos son en gran medida problemas relacionados con la implementación, pero el rendimiento para las operaciones básicas también es importante. El rendimiento también es complejo porque depende del motor y el uso. Realice mis pruebas con un grano de sal, ya que no puedo descartar ningún error (tengo que apresurar esto). También debe ejecutar sus propias pruebas para confirmar, ya que la mía examina solo escenarios simples muy específicos para dar solo una indicación aproximada. Según las pruebas en Chrome para objetos / mapas muy grandes, el rendimiento de los objetos es peor debido a la eliminación, que aparentemente es proporcional al número de teclas en lugar de O (1):

Object Set Took: 146
Object Update Took: 7
Object Get Took: 4
Object Delete Took: 8239
Map Set Took: 80
Map Update Took: 51
Map Get Took: 40
Map Delete Took: 2

Chrome claramente tiene una gran ventaja al obtener y actualizar, pero el rendimiento de eliminación es horrible. Los mapas usan una pequeña cantidad de memoria en este caso (sobrecarga) pero con solo un Objeto / Mapa siendo probado con millones de claves, el impacto de la sobrecarga para los mapas no se expresa bien. Con la administración de memoria, los objetos también parecen liberarse antes si estoy leyendo el perfil correctamente, lo que podría ser un beneficio a favor de los objetos.

En Firefox para este punto de referencia particular, es una historia diferente:

Object Set Took: 435
Object Update Took: 126
Object Get Took: 50
Object Delete Took: 2
Map Set Took: 63
Map Update Took: 59
Map Get Took: 33
Map Delete Took: 1

Debo señalar de inmediato que en este punto de referencia en particular, eliminar de los objetos en Firefox no está causando ningún problema, sin embargo, en otros puntos de referencia ha causado problemas, especialmente cuando hay muchas claves como en Chrome. Los mapas son claramente superiores en Firefox para grandes colecciones.

Sin embargo, este no es el final de la historia, ¿qué pasa con muchos objetos pequeños o mapas? He hecho un punto de referencia rápido de esto, pero no uno exhaustivo (configuración / obtención) que funciona mejor con un pequeño número de teclas en las operaciones anteriores. Esta prueba trata más sobre memoria e inicialización.

Map Create: 69    // new Map
Object Create: 34 // {}

Una vez más, estas cifras varían, pero básicamente Object tiene una buena ventaja. En algunos casos, la ventaja para Objetos sobre mapas es extrema (~ 10 veces mejor) pero en promedio fue alrededor de 2-3 veces mejor. Parece que los picos de rendimiento extremos pueden funcionar en ambos sentidos. Solo probé esto en Chrome y la creación para perfilar el uso de memoria y los gastos generales. Me sorprendió bastante ver que en Chrome parece que Maps con una tecla usa alrededor de 30 veces más memoria que los Objetos con una tecla.

Para probar muchos objetos pequeños con todas las operaciones anteriores (4 teclas):

Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139

En términos de asignación de memoria, estos se comportaron igual en términos de liberación / GC, pero Map utilizó 5 veces más memoria. Esta prueba usó 4 teclas donde, como en la última prueba, solo configuré una tecla para que esto explicara la reducción en la sobrecarga de memoria. Ejecuté esta prueba varias veces y Map / Object son más o menos compactos en general para Chrome en términos de velocidad general. En Firefox para objetos pequeños, existe una clara ventaja de rendimiento sobre los mapas en general.

Por supuesto, esto no incluye las opciones individuales que pueden variar enormemente. No recomendaría micro-optimización con estas cifras. Lo que puede sacar de esto es que, como regla general, considere Maps con más fuerza para los almacenes de valores clave muy grandes y los objetos para almacenes de valores clave pequeños.

Más allá de eso, la mejor estrategia con estos dos es implementarlo y hacer que funcione primero. Al crear un perfil, es importante tener en cuenta que a veces las cosas que no pensarías que serían lentas al mirarlas pueden ser increíblemente lentas debido a las peculiaridades del motor como se ve con el caso de eliminación de clave de objeto.

jgmjgm
fuente
La falta de serialización ha sido un verdadero dolor para muchos desarrolladores. Mire el voto a favor de ¿Cómo persisto un mapa ES6 en el almacenamiento local (o en otro lugar)? y ¿Cómo JSON.stringify un mapa ES6? .
Franklin Yu
¿El número está en milisegundos, bytes u objetos totales?
StefansArya
Tomó mucho ms (algo que se tomó es corto para decir algo usado, por lo que en este caso usa tiempo de inactividad). Aunque esta es una prueba antigua y ya no tengo el código de referencia. Probablemente es muy diferente ahora. El problema de eliminación, por ejemplo, creo que está solucionado.
jgmjgm
27

No creo que se hayan mencionado los siguientes puntos en las respuestas hasta ahora, y pensé que valdría la pena mencionarlos.


Los mapas pueden ser más grandes

En Chrome puedo obtener 16,7 millones de pares clave / valor con Mapvs. 11,1 millones con un objeto regular. Casi exactamente un 50% más de pares con a Map. Ambos ocupan aproximadamente 2 GB de memoria antes de bloquearse, por lo que creo que puede tener que ver con la limitación de memoria por cromo ( Editar : Sí, intente llenar 2 Mapsy solo obtendrá 8.3 millones de pares antes de que se bloquee). Puede probarlo usted mismo con este código (ejecútelos por separado y no al mismo tiempo, obviamente):

var m = new Map();
var i = 0;
while(1) {
    m.set(((10**30)*Math.random()).toString(36), ((10**30)*Math.random()).toString(36));
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}
// versus:
var m = {};
var i = 0;
while(1) {
    m[((10**30)*Math.random()).toString(36)] = ((10**30)*Math.random()).toString(36);
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}

Los objetos ya tienen algunas propiedades / claves

Este me ha hecho tropezar antes. Objetos normales tienen toString, constructor, valueOf, hasOwnProperty, isPrototypeOfy un montón de otras propiedades preexistentes. Esto puede no ser un gran problema para la mayoría de los casos de uso, pero antes me ha causado problemas.

Los mapas pueden ser más lentos:

Debido a la .getsobrecarga de llamadas de función y la falta de optimización interna, Map puede ser considerablemente más lento que un objeto JavaScript antiguo para algunas tareas.


fuente
1
En su opinión, ¿la semántica supera el rendimiento aquí? Los mapas suenan perfectos si necesita un diccionario, pero es difícil aceptar una búsqueda más lenta. ¿No es una búsqueda rápida el punto completo de los diccionarios?
user2954463
3
¡Sin duda, con los viejos objetos simples si usted está muy bien con 11 millones de pares clave / valor y no se preocupan por las teclas de pre-existentes, como toString, constructor, etc. (es decir, las teclas son muy poco probable que chocan con ellos). Es más fácil trabajar con ellos, por ejemplo, el incremento lo es obj[i] = (obj[i] || 0) + 1, mientras que con Mapeso map.set(i, (map.get(i) || 0) + 1)todavía no es tan malo, pero solo muestra cómo las cosas pueden complicarse innecesariamente. Los mapas definitivamente tienen sus casos de uso, pero a menudo un objeto simple servirá.
Tenga en cuenta que usted puede deshacerse de la forma predeterminada toString, constructor, (etc.) las propiedades del objeto de escritura obj = Object.create(null)en lugar de obj = {}.
17

Los objetos pueden comportarse como diccionarios porque Javascript se escribe dinámicamente, lo que le permite agregar o eliminar propiedades en un objeto en cualquier momento.

Pero la nueva Map()funcionalidad es mucho mejor porque:

  • Proporciona get, set, has, y deletemétodos.
  • Acepta cualquier tipo de teclas en lugar de solo cadenas.
  • Proporciona un iterador para un for-ofuso fácil y mantiene el orden de los resultados.
  • No tiene casos extremos con prototipos y otras propiedades que se muestran durante la iteración o la copia.
  • Es compatible con millones de artículos.
  • Es muy rápido y sigue haciéndose más rápido a medida que los motores de JavaScript mejoran.

El 99% del tiempo solo debes usar a Map(). Sin embargo, si solo usa claves basadas en cadenas y necesita un rendimiento de lectura máximo, los objetos pueden ser una mejor opción.

El detalle es que (casi todos) los motores javascript compilan objetos en clases de C ++ en segundo plano. Estos tipos se almacenan en caché y se reutilizan por su "esquema", por lo que cuando crea un nuevo objeto con las mismas propiedades exactas, el motor reutilizará una clase de fondo existente. La ruta de acceso para las propiedades en estas clases está muy optimizada y es mucho más rápida que la búsqueda de a Map().

Agregar o quitar una propiedad hace que la clase de respaldo en caché se vuelva a compilar, por lo que usar un objeto como diccionario con muchas adiciones y eliminaciones de claves es muy lento, pero las lecturas y la asignación de claves existentes sin cambiar el objeto son muy rápidas.

Por lo tanto, si tiene una carga de trabajo de lectura pesada de escritura única con teclas de cadena, use un objectcomo diccionario especializado de alto rendimiento, pero para todo lo demás use a Map().

Mani Gandham
fuente
Object también proporciona get set has deletefuncionalidad, etc., no es tan elegante (pero tampoco está mal). ¿De qué manera es Mapmás fácil de usar para iterar? No estoy seguro de poder estar de acuerdo.
Andrew
@ Andrew Estoy tomando los métodos, y la funcionalidad también es diferente dependiendo de lo que estés usando y el resultado. La iteración es más fácil porque el prototipo y las propiedades nativas no se muestran en el bucle y utiliza un iterador JS normal que mantiene el mismo orden.
Mani Gandham
11

Además de las otras respuestas, descubrí que los mapas son más difíciles de manejar y detallados para operar que los objetos.

obj[key] += x
// vs.
map.set(map.get(key) + x)

Esto es importante, porque el código más corto es más rápido de leer, más expresivo directamente y se mantiene mejor en la cabeza del programador .

Otro aspecto: porque set () devuelve el mapa, no el valor, es imposible encadenar las asignaciones.

foo = obj[key] = x;  // Does what you expect
foo = map.set(key, x)  // foo !== x; foo === map

La depuración de mapas también es más dolorosa. A continuación, no puede ver qué teclas hay en el mapa. Tendrías que escribir código para hacer eso.

Buena suerte evaluando un iterador de mapas

Los objetos pueden ser evaluados por cualquier IDE:

WebStorm evaluando un objeto

Dan Dascalescu
fuente
44
Dado todo esto, parece que el mapa es una optimización prematura.
PRMan
10

Resumen:

  • Object: Una estructura de datos en la que los datos se almacenan como pares de valores clave. En un objeto, la clave debe ser un número, una cadena o un símbolo. El valor puede ser cualquier cosa, también otros objetos, funciones, etc. Un objeto es una estructura de datos no ordenada , es decir, no se recuerda la secuencia de inserción de pares de valores clave
  • ES6 Map: Una estructura de datos en la que los datos se almacenan como pares de valores clave. En el que una clave única se asigna a un valor . Tanto la clave como el valor pueden estar en cualquier tipo de datos . Un mapa es una estructura de datos iterable, esto significa que se recuerda la secuencia de inserción y que podemos acceder a los elementos, por ejemplo, en un for..ofbucle

Diferencias clave

  • A Mapes ordenado e iterable, mientras que un objeto no está ordenado ni es iterable

  • Podemos poner cualquier tipo de datos como Mapclave, mientras que los objetos solo pueden tener un número, una cadena o un símbolo como clave.

  • A Maphereda de Map.prototype. Esto ofrece todo tipo de funciones y propiedades de utilidad que hacen que trabajar con Mapobjetos sea mucho más fácil.

Ejemplo:

objeto:

let obj = {};

// adding properties to a object
obj.prop1 = 1;
obj[2]    =  2;

// getting nr of properties of the object
console.log(Object.keys(obj).length)

// deleting a property
delete obj[2]

console.log(obj)

Mapa:

const myMap = new Map();

const keyString = 'a string',
    keyObj = {},
    keyFunc = function() {};

// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');

console.log(myMap.size); // 3

// getting the values
console.log(myMap.get(keyString));    // "value associated with 'a string'"
console.log(myMap.get(keyObj));       // "value associated with keyObj"
console.log(myMap.get(keyFunc));      // "value associated with keyFunc"

console.log(myMap.get('a string'));   // "value associated with 'a string'"
                         // because keyString === 'a string'
console.log(myMap.get({}));           // undefined, because keyObj !== {}
console.log(myMap.get(function() {})) // undefined, because keyFunc !== function () {}

fuente: MDN

Willem van der Veen
fuente
4

Además de ser iterables en un orden bien definido, y la capacidad de usar valores arbitrarios como claves (excepto -0), los mapas pueden ser útiles por las siguientes razones:

  • La especificación exige que las operaciones de mapa sean sublineales en promedio.

    Cualquier implementación no estúpida del objeto usará una tabla hash o similar, por lo que las búsquedas de propiedades probablemente serán constantes en promedio. Entonces los objetos podrían ser incluso más rápidos que los mapas. Pero eso no es requerido por la especificación.

  • Los objetos pueden tener comportamientos inesperados desagradables.

    Por ejemplo, supongamos que no configuró ninguna foopropiedad en un objeto recién creado obj, por lo que espera obj.foovolver indefinido. Perofoo podría ser propiedad incorporada heredada de Object.prototype. O intentas crear obj.foousando una tarea, pero algún setter enObject.prototype carreras lugar de almacenar su valor.

    Los mapas evitan este tipo de cosas. Bueno, a menos que algún script se equivoque Map.prototype. Y Object.create(null)funcionaría también, pero luego se pierde la sintaxis de inicializador de objeto simple.

Oriol
fuente
4

¿Cuándo usar Maps en lugar de objetos JavaScript simples?

El simple objeto JavaScript {key: 'value'} contiene datos estructurados. Pero el objeto JS simple tiene sus limitaciones:

  1. Solo se pueden usar cadenas y símbolos como claves de objetos. Si usamos cualquier otra cosa, digamos, los números como claves de un objeto, entonces al acceder a esas claves veremos que esas claves se convertirán en cadenas implícitamente, lo que nos hará perder la consistencia de los tipos. const names = {1: 'uno', 2: 'dos'}; Object.keys (nombres); // ['1', '2']

  2. Hay posibilidades de sobrescribir accidentalmente las propiedades heredadas de los prototipos escribiendo identificadores JS como nombres clave de un objeto (por ejemplo, toString, constructor, etc.)

  3. No se puede usar otro objeto como clave de un objeto, por lo que no se puede escribir información adicional para un objeto escribiendo ese objeto como clave de otro objeto y el valor de ese otro objeto contendrá la información adicional

  4. Los objetos no son iteradores

  5. El tamaño de un objeto no se puede determinar directamente

Maps resuelve estas limitaciones de los objetos, pero debemos considerar Maps como complemento para los objetos en lugar de reemplazarlos. Básicamente, Map es solo una matriz de matrices, pero debemos pasar esa matriz de matrices al objeto Map como argumento con una nueva palabra clave; de ​​lo contrario, solo para la matriz de matrices, las propiedades y métodos útiles de Map no están disponibles. Y recuerde que los pares clave-valor dentro de la matriz de matrices o el Mapa deben estar separados solo por comas, sin dos puntos como en los objetos simples.

3 consejos para decidir si usar un Mapa o un Objeto:

  1. Use mapas sobre objetos cuando las claves son desconocidas hasta el tiempo de ejecución porque las claves formadas por la entrada del usuario o sin saberlo pueden romper el código que usa el objeto si esas claves sobrescriben las propiedades heredadas del objeto, por lo que el mapa es más seguro en esos casos. También use mapas cuando todas las teclas sean del mismo tipo y todos los mapas sean del mismo tipo.

  2. Utilice mapas si es necesario almacenar valores primitivos como claves.

  3. Use objetos si necesitamos operar sobre elementos individuales.

Los beneficios de usar Maps son:

1. Map acepta cualquier tipo de clave y conserva el tipo de clave:

Sabemos que si la clave del objeto no es una cadena o símbolo, JS la transforma implícitamente en una cadena. Por el contrario, Map acepta cualquier tipo de claves: cadena, número, booleano, símbolo, etc. y Map conserva el tipo de clave original. Aquí usaremos número como clave dentro de un Mapa y seguirá siendo un número:

const numbersMap= new Map();

numbersMap.set(1, 'one');

numbersMap.set(2, 'two');

const keysOfMap= [...numbersMap.keys()];

console.log(keysOfMap);                        // [1, 2]

Dentro de un mapa, incluso podemos usar un objeto completo como clave. Puede haber ocasiones en las que queramos almacenar algunos datos relacionados con el objeto, sin adjuntar estos datos dentro del objeto en sí, de modo que podamos trabajar con objetos lean pero queramos almacenar cierta información sobre el objeto. En esos casos, necesitamos usar Map para poder hacer Object como clave y los datos relacionados del objeto como valor.

const foo= {name: foo};

const bar= {name: bar};

const kindOfMap= [[foo, 'Foo related data'], [bar, 'Bar related data']];

Pero la desventaja de este enfoque es la complejidad de acceder al valor por clave, ya que tenemos que recorrer toda la matriz para obtener el valor deseado.

function getBy Key(kindOfMap, key) {
    for (const [k, v]  of kindOfMap) {
        if(key === k) {
            return v;
        }
    }
    return undefined;
}

getByKey(kindOfMap, foo);            // 'Foo related data'

Podemos resolver este problema de no obtener acceso directo al valor utilizando un Mapa adecuado.

const foo= {name: 'foo'};

const bar= {name: 'bar'};

const myMap= new Map();

myMap.set(foo, 'Foo related data');
myMap.set(bar, 'Bar related data');

console.log(myMap.get(foo));            // 'Foo related data'

Podríamos haber hecho esto usando WeakMap, solo tenemos que escribir, const myMap = new WeakMap (). Las diferencias entre Map y WeakMap son que WeakMap permite la recolección de basura de claves (aquí objetos) para evitar fugas de memoria, WeakMap acepta solo objetos como claves y WeakMap ha reducido el conjunto de métodos.

2. El mapa no tiene restricción sobre los nombres clave:

Para objetos JS simples, podemos sobrescribir accidentalmente la propiedad heredada del prototipo y puede ser peligroso. Aquí sobrescribiremos la propiedad toString () del objeto actor:

const actor= {
    name: 'Harrison Ford',
    toString: 'Actor: Harrison Ford'
};

Ahora definamos un fn isPlainObject () para determinar si el argumento proporcionado es un objeto simple y este fn usa el método toString () para verificarlo:

function isPlainObject(value) {
    return value.toString() === '[object Object]';
}

isPlainObject(actor);        // TypeError : value.toString is not a function

// this is because inside actor object toString property is a string instead of inherited method from prototype

El mapa no tiene ninguna restricción en los nombres de clave, podemos usar nombres de clave como toString, constructor, etc. Aquí, aunque el objeto actorMap tiene una propiedad llamada toString pero el método toString () heredado del prototipo del objeto actorMap funciona perfectamente.

const actorMap= new Map();

actorMap.set('name', 'Harrison Ford');

actorMap.set('toString', 'Actor: Harrison Ford');

function isMap(value) {
  return value.toString() === '[object Map]';
}

console.log(isMap(actorMap));     // true

Si tenemos una situación en la que la entrada del usuario crea claves, debemos tomar esas claves dentro de un Mapa en lugar de un objeto plano. Esto se debe a que el usuario puede elegir un nombre de campo personalizado como, toString, constructor, etc., entonces dichos nombres de clave en un objeto plano pueden potencialmente romper el código que luego usa este objeto. Entonces, la solución correcta es vincular el estado de la interfaz de usuario a un mapa, no hay forma de romper el Mapa:

const userCustomFieldsMap= new Map([['color', 'blue'], ['size', 'medium'], ['toString', 'A blue box']]);

3. El mapa es iterable:

Para iterar las propiedades de un objeto plano necesitamos Object.entries () u Object.keys (). Object.entries (plainObject) devuelve una matriz de pares de valores clave extraídos del objeto, luego podemos desestructurar esas claves y valores y podemos obtener claves y valores normales.

const colorHex= {
  'white': '#FFFFFF',
  'black': '#000000'
}

for(const [color, hex] of Object.entries(colorHex)) {
  console.log(color, hex);
}
//
'white' '#FFFFFF'   
'black' '#000000'

Como los mapas son iterables, es por eso que no necesitamos métodos de entradas () para iterar sobre un mapa y la desestructuración de la clave, la matriz de valores se puede hacer directamente en el mapa, ya que dentro de un mapa cada elemento vive como una matriz de pares de valores clave separados por comas .

const colorHexMap= new Map();
colorHexMap.set('white', '#FFFFFF');
colorHexMap.set('black', '#000000');


for(const [color, hex] of colorHexMap) {
  console.log(color, hex);
}
//'white' '#FFFFFF'   'black' '#000000'

También map.keys () devuelve un iterador sobre las claves y map.values ​​() devuelve un iterador sobre los valores.

4. Podemos saber fácilmente el tamaño de un mapa

No podemos determinar directamente el número de propiedades en un objeto plano. Necesitamos un ayudante fn como, Object.keys () que devuelve una matriz con las claves del objeto y luego, usando la propiedad de longitud, podemos obtener el número de claves o el tamaño del objeto plano.

const exams= {'John Rambo': '80%', 'James Bond': '60%'};

const sizeOfObj= Object.keys(exams).length;

console.log(sizeOfObj);       // 2

Pero en el caso de Mapas, podemos tener acceso directo al tamaño del Mapa usando la propiedad map.size.

const examsMap= new Map([['John Rambo', '80%'], ['James Bond', '60%']]);

console.log(examsMap.size);
djb7861
fuente
1

Estos dos consejos pueden ayudarlo a decidir si usar un Mapa o un Objeto:

  • Use mapas sobre objetos cuando las claves son desconocidas hasta el tiempo de ejecución, y cuando todas las claves son del mismo tipo y todos los valores son del mismo tipo.

  • Use mapas en caso de que sea necesario almacenar valores primitivos como claves porque el objeto trata cada clave como una cadena, ya sea un valor numérico, un valor booleano o cualquier otro valor primitivo.

  • Use objetos cuando haya lógica que opere en elementos individuales.

Fuente: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections#Object_and_Map_compared


fuente
2
Estos consejos no parecen particularmente útiles, especialmente porque tienden a no ser fáciles de dividir según esos criterios. No entiendo por qué los mapas son un beneficio cuando las claves / valores son del mismo tipo. Parece más como si tratara de decir usar objetos como clases / estructuras, mapas como colecciones. El segundo está mal escrito y no llega al punto. Realmente significa usar mapas cuando tiene tipos equivalentes de cadenas mixtas ("1" y 1) o cuando necesita / desea preservar tipos de clave. Lo último, creo que es lo mismo que lo primero, está asumiendo que no sabes qué es un objeto, por lo que es vago.
jgmjgm
1

Este es un camino corto para recordarlo: KOI

  1. Llaves. La clave del objeto es cadenas o símbolos. Las teclas de mapa también pueden ser números (1 y "1" son diferentes), objetos NaN, etc. Se usa ===para distinguir entre teclas, con una excepción, NaN !== NaNpero puede usarNaN como una tecla.
  2. Orden. Se recuerda el orden de inserción. Entonces [...map]o[...map.keys()] tiene un orden particular.
  3. Interfaz. Objeto: obj[key]o obj.a(en algún idioma, []y []=son realmente parte de la interfaz). Mapa tiene get(), set(), has(), delete()etc. Nota que se puede utilizar map[123], pero que lo está utilizando como un objeto JS llanura.
nonopolaridad
fuente
0

De acuerdo con Mozilla

Objeto vs Mapa en JavaScript de forma breve con ejemplos.

Objeto: sigue el mismo concepto que el de mapa, es decir, usar un par clave-valor para almacenar datos. Pero existen pequeñas diferencias que hacen que el mapa tenga un mejor desempeño en ciertas situaciones.

Map- es una estructura de datos que ayuda a almacenar los datos en forma de pares. El par consta de una clave única y un valor asignado a la clave. Ayuda a prevenir la duplicidad.

Diferencias clave

  • El mapa es una instancia de un objeto, pero viceversa, no es cierto.

var map = new Map();
var obj = new Object(); 
console.log(obj instanceof Map);   // false
console.log(map instanceof Object);  // true

  • En Object, el tipo de datos del campo clave está restringido a enteros, cadenas y símbolos. Mientras que en Map, el campo clave puede ser de cualquier tipo de datos (entero, una matriz, un objeto)

var map = new Map();//Empty 
map.set(1,'1');
map.set('one', 1);
map.set('{}', {name:'Hello world'});
map.set(12.3, 12.3)
map.set([12],[12345])

for(let [key,value] of map.entries())
  console.log(key+'---'+value)

  • En el mapa, se conserva el orden original de los elementos. Esto no es cierto en el caso de los objetos.

let obj ={
  1:'1',
  'one':1,
  '{}': {name:'Hello world'},
  12.3:12.3,
  [12]:[100]
}
console.log(obj)

Vahid Akhtar
fuente