Mapa vs Objeto en JavaScript


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

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

Eso me confundió. Los objetos JavaScript regulares son diccionarios, entonces, ¿en qué se diferencia un Map de un diccionario? Conceptualmente, son idénticos (según ¿Cuál es la diferencia entre un Mapa y un Diccionario?)

Las referencias de la documentación chromestatus no ayudan o bien:

Los objetos Map son colecciones de pares clave/valor donde tanto las claves como los valores pueden ser valores arbitrarios del lenguaje ECMAScript. Un valor de clave distinto solo puede aparecer en un par clave / valor dentro de la colección del mapa. Valores de clave distintos según se discrimine 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. Map object debe implementarse utilizando tablas hash u otros mecanismos que, en promedio, proporcionan tiempos de acceso que son sublineales en el número de elementos de la colección. Las estructuras de datos utilizadas en esta especificación de objetos de mapa solo están destinadas a describir la semántica observable requerida de los objetos de mapa. No pretende ser un modelo de implementación viable.

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

¿Por qué JavaScript obtiene un objeto Map (bien soportado)? ¿Qué hace?

Author: Community, 2013-08-31

7 answers

Según mozilla:

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

Y

Los objetos son similares a los mapas en que ambos permiten establecer claves a valores, recuperar esos valores, eliminar claves y detectar si algo está almacenado en una llave. Debido a esto, los objetos se han utilizado como Mapas históricamente; sin embargo, hay diferencias importantes entre los 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 puede ser omitido usando map = Object.crear(null). El 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 que tiene que mantener manualmente seguimiento 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 el mismo tipo.

Use objetos cuando hay lógica que opera 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 han deseado durante mucho tiempo, en parte porque garantiza el mismo rendimiento en todos los navegadores. Así que para mí eso es grande.

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

 180
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-30 21:59:14

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 hago obj[123] = true y luego Objecto.teclas (obj) entonces obtendré ["123"] en lugar de [123]. Un mapa preservaría el tipo de la clave y devolvería [123] lo cual es genial. Los mapas también permiten utilizar objetos como claves. Tradicionalmente, para hacer esto, tendría que dar a los objetos algún tipo de identificador único para hash (no creo que nunca haya visto nada como getObjectId en JS como parte del estándar). Los mapas también garantizan la preservación del orden, por lo que son 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 JS, lo que los distingue de mapear significativamente más allá de la diferencia en el soporte de claves.

Una ventaja inmediata es que tiene soporte sintáctico para objetos que facilitan 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 en absoluto. De forma predeterminada, si desea usar Objetos como una tabla hash, estarán contaminados y a menudo tendrá que llamar a hasOwnProperty en ellos al acceder a las propiedades. Puede ver aquí cómo los objetos están contaminados por defecto y cómo crear objetos que esperemos que no estén contaminados para usarlos 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 de los objetos no es solo algo que hace que el código sea más molesto, más lento, etc., pero también puede tener consecuencias potenciales para la seguridad.

Los objetos no son tablas hash puras, pero están tratando de hacer más. Usted tiene dolores de cabeza como hasOwnProperty, no ser capaz de obtener la longitud fácilmente (Objeto.llaves (obj).longitud) y así sucesivamente. Los objetos no están destinados a ser utilizados puramente como mapas hash, sino también como objetos dinámicos extensibles y, por lo tanto, cuando los utiliza como tablas hash puras, surgen problemas.

Comparación / Lista de varios 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, abordadas, metodologías, etc. con diferentes altibajos (rendimiento, conciso, portátil, extensible, 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 los mapas de preservar los tipos de clave, así como ser capaz de soportar cosas como objetos como claves que están aislados de los efectos secundarios que los objetos tienen mucho. 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 Proxy, sin embargo, el rendimiento y el uso de memoria es sombrío, de hecho, crear su propio proxy que se parece a Map for Objects actualmente funciona mejor que Proxy.

Una desventaja sustancial para los mapas es que no son compatibles con JSON directamente. El análisis es posible, pero tiene varias aristas:

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á una golpe de rendimiento grave 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 está usando Mapas puramente, pero tendrá problemas cuando esté mezclando tipos o utilizando valores no escalares como claves (no es que JSON sea perfecto con ese tipo de problema, es DECIR, referencia de objeto circular). No lo he probado, pero lo más probable es que dañe gravemente el rendimiento en comparación con stringify.

Otros lenguajes de scripting a menudo no tienen tales problemas, ya que tienen tipos no escalares explícitos para Map, Object y Array. El desarrollo web es a menudo un dolor con los tipos no escalares donde tienes que lidiar con cosas como PHP fusiona Matriz/Mapa con Objeto usando A/M para propiedades y JS fusiona Mapa/Objeto con Matriz extendiendo M/O. La fusión de tipos complejos es la pesadilla del diablo de los lenguajes de scripting de alto nivel.

Hasta ahora se trata en gran medida de cuestiones relacionadas con la aplicació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. Tome mis pruebas con un grano de sal, ya que no puedo descartar ningún error(tengo que apresurarme). También debe ejecutar sus propias pruebas para confirmar como mina examinar solo escenarios simples muy específicos para dar una indicación aproximada solamente. De acuerdo con las pruebas en Chrome para objetos muy grandes / mapas el rendimiento de los objetos es peor debido a eliminar que es aparentemente de alguna manera 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 con la obtención y actualización, pero el rendimiento de eliminación es horrible. Los mapas usan una pequeña cantidad más de memoria en este caso (sobrecarga), pero con solo un Objeto/Mapa que se está probando con millones de teclas, el impacto de la sobrecarga para los mapas no se expresa bien. Con los objetos de gestión de memoria también parecen liberar antes si estoy leyendo el perfil correctamente, lo que podría ser un beneficio a favor de objeto.

En FireFox para este punto de referencia en 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 inmediatamente que en este punto de referencia en particular la eliminación de objetos en FireFox no está causando ningún problema, sin embargo, en otros puntos de referencia que ha causado problemas, especialmente cuando hay muchas claves al igual que 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? Tengo hecho un punto de referencia rápido de esto, pero no uno exhaustivo (configuración / obtención) de los cuales funciona mejor con un pequeño número de claves en las operaciones anteriores. Esta prueba es más sobre la memoria y la inicialización.

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

Nuevamente 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 de alrededor de 2-3 veces mejor. Parece que los picos de rendimiento extremos pueden funcionar en ambos sentidos. Solo probé esto en Chrome y creación de perfiles de uso de memoria y sobrecarga. Me sorprendió bastante ver que en Chrome parece que los mapas con una clave utilizan alrededor de 30 veces más memoria que los objetos con una clave.

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 de la misma manera en términos de liberar/GC, pero Map usó 5 veces más memoria. Esta prueba utilizó 4 llaves donde como en la prueba pasada fijé solamente una llave así que esto explicaría la reducción en la memoria de arriba. Ejecuté esta prueba un par de veces y Mapa / Objeto son más o menos cuello y cuello en general para Chrome en términos de velocidad general. En FireFox para objetos pequeños hay una ventaja de rendimiento definitiva sobre los mapas en general.

Esto, por supuesto, no incluye las opciones individuales que podrían variar enormemente. No lo recomiendo micro-optimización con estas figuras. Lo que puede obtener de esto es que, como regla general, considere los mapas con más fuerza para almacenes de valor clave muy grandes y objetos para pequeños almacenes de valores clave.

Más allá de eso, la mejor estrategia con estos dos es implementarla y simplemente hacer que funcione primero. Al perfilar, 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.

 70
Author: jgmjgm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-16 12:45:47

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


Los mapas pueden ser más grandes

En chrome puedo conseguir 16.7 millones de pares de clave/valor con Map vs 11.1 millones con un objeto regular. Casi exactamente 50% más pares con un Map. Ambos ocupan alrededor de 2 GB de memoria antes de que se bloqueen, por lo que creo que puede tener que ver con la limitación de memoria por chrome (Editar : Sí, intente llenando 2 Maps y solo obtienes 8.3 millones de pares cada uno antes de que se bloquee). Puede probarlo usted mismo con este código (ejecútelo 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. Los objetos tienen toString, constructor, valueOf, hasOwnProperty, isPrototypeOf y un montón de otras propiedades ya existentes. Esto puede no ser un gran problema para la mayoría de los casos de uso, pero me ha causado problemas antes.

Mapas puede ser más lento:

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

 12
Author: Joe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-26 11:54:46

Además de las otras respuestas, he encontrado 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 directamente expresivo, y mejor mantenido en la cabeza del programador.

Otro aspecto: debido a que 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

Depurar mapas también es más doloroso. A continuación, en realidad no se puede ver qué teclas están 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

 5
Author: Dan Dascalescu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-04 13:20:11

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 debido a las siguientes razones:

  • La especificación obliga a que las operaciones del mapa sean sublineales en promedio.

    Cualquier implementación no estúpida de object 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 el spec.

  • Los objetos pueden tener comportamientos desagradables e inesperados.

    Por ejemplo, supongamos que no ha establecido ninguna propiedad foo a un objeto recién creado obj, por lo que espera que obj.foo devuelva undefined. Pero foo podría ser una propiedad incorporada heredada de Object.prototype. O intenta crear obj.foo usando una asignación, pero algún configurador en Object.prototype se ejecuta en lugar de almacenar su valor.

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

 2
Author: Oriol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-29 23:02:13

Los objetos pueden comportarse como diccionarios porque Javascript se escribe dinámicamente (y no ha habido una buena alternativa hasta ahora), pero realmente no están destinados a ser.

La nueva funcionalidad Map() es mucho más agradable porque tiene los métodos get/set/has/delete esperados que esperarías, mientras que también acepta cualquier tipo para las claves en lugar de solo cadenas. Es más fácil de usar cuando se itera, y no tiene casos extremos con prototipos y otras propiedades que aparecen. También es muy rápido y mantiene cada vez más rápido a medida que los motores mejoran. Para el 99% de los escenarios, solo debe usar un Map().

Sin embargo, si solo está utilizando claves basadas en cadenas y necesita el máximo rendimiento de lectura, los objetos pueden ayudar. El detalle es que (casi todos) los motores javascript compilan objetos hasta las clases de C++ en segundo plano. Por lo general, estos tipos son buscados por el esquema, lo que significa que cuando crea un nuevo objeto con las mismas propiedades exactas que un objeto existente, el motor reutilizará un fondo existente clase. Del mismo modo, la ruta de acceso para las propiedades en estas clases de respaldo es muy optimizada y mucho más rápida que la redirección y búsqueda de métodos de un Map().

Sin embargo, cuando agrega o elimina una propiedad, la clase de respaldo y las rutas de búsqueda en caché se borran y recompilan sobre la marcha, por lo que pierde rendimiento cuando usa un objeto como diccionario con mucha creación y eliminación de claves, pero las lecturas y la asignación de una clave existente son muy rápidas.

Así que si eres haciendo muchas lecturas, luego mira object como un diccionario especializado de alto rendimiento, pero para todo lo demás, usa un Map().

 1
Author: Mani Gandham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-08 03:14:35

Estos dos consejos pueden ayudarle a decidir si utilizar un Mapa o un Objeto:

  • Usar 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.

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

  • Usar objetos cuando hay lógica que funciona sobre elementos individuales.

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

 0
Author: Cakedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-21 20:56:47