how - javascript map values
Mapa vs Objeto en JavaScript (8)
Acabo de descubrir chromestatus.com y, después de perder varias horas de mi día, encontré esta entrada de función :
Mapa: los objetos del mapa son simples mapas 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? )
La documentación de las referencias de chromestatus tampoco ayuda:
Los objetos del mapa son colecciones de pares clave / valor donde tanto las claves como los valores pueden ser valores de lenguaje ECMAScript arbitrarios. Un valor de clave distinto solo puede aparecer en un par de clave / valor dentro de la colección del Mapa. Distingue los valores de clave según se discrimina usando un 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, proporcionen tiempos de acceso que sean sublineales en el número de elementos de la colección. Las estructuras de datos utilizadas en esta especificación de objetos del Mapa solo pretenden describir la semántica observable requerida de los objetos del Mapa. No pretende ser un modelo de implementación viable.
... todavía me suena como un objeto, tan claramente que me he perdido algo.
¿Por qué está ganando JavaScript un objeto de Map
(bien soportado)? ¿Qué hace?
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, la secuencia de inserción de pares de valores clave no se recuerda -
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 la secuencia de inserción es recordada y que podemos acceder a los elementos, por ejemplo, en un buclefor..of
Diferencias clave:
Un
Map
está ordenado e iterable, mientras que un objeto no está ordenado ni es iterable.Podemos colocar cualquier tipo de datos como clave de
Map
, mientras que los objetos solo pueden tener un número, cadena o símbolo como clave.Un
Map
hereda deMap.prototype
. Esto ofrece todo tipo de funciones de utilidad y propiedades que facilitan mucho el trabajo con los objetos deMap
.
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 () {}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
Además de las otras respuestas, he descubierto 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 mejor guardado 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 puedes ver qué claves están en el mapa. Tendrías que escribir código para hacer eso.
Los objetos pueden ser evaluados por cualquier IDE:
Además de ser iterable 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 un objeto utilizará una tabla hash o similar, por lo que las búsquedas de propiedades probablemente sean 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 propiedad
foo
para un objetoobj
recién creado, por lo que espera queobj.foo
regrese como no definido. Perofoo
podría ser una propiedad incorporada heredada deObject.prototype
. O intenta crearobj.foo
usando una asignación, pero se ejecuta algún setter enObject.prototype
en lugar de almacenar tu valor.Los mapas evitan este tipo de cosas. Bueno, a menos que algún script se
Map.prototype
conMap.prototype
. YObject.create(null)
también funcionaría, pero luego se pierde la sintaxis del inicializador de objeto simple.
Estos dos consejos pueden ayudarlo a decidir si usar un Mapa o un Objeto:
Use mapas sobre objetos cuando las claves no se conocen 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 a cada clave como una cadena, ya sea un valor numérico, un valor booleano o cualquier otro valor primitivo.
Usar objetos cuando hay lógica que opera sobre elementos individuales.
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] = verdadero y luego Object.keys (obj), obtendré ["123"] en lugar de [123]. Un Mapa conservaría el tipo de clave y devolvería [123] lo que es excelente. Los mapas también te permiten usar objetos como claves. Tradicionalmente, para hacer esto, tendrías que darles a los objetos algún tipo de identificador único (hash) (no creo que haya visto nada como getObjectId en JS como parte del estándar). Los mapas también garantizan la conservación del orden, por lo que son mejores para la conservación y, a veces, pueden ahorrarle la necesidad de hacer algunas clases.
Entre mapas y objetos en la práctica hay varios pros y contras. Los objetos obtienen ventajas y desventajas al integrarse muy estrechamente en el núcleo de JS, 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 tienes soporte directo para ello con JSON. Cuando se utiliza 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 llamar a hasOwnProperty cuando accedan a las propiedades. Aquí puede ver cómo, por defecto, los objetos están contaminados y cómo crear objetos no 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 en los objetos no solo es 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 puras tablas hash, sino que intentan hacer más. Tiene dolores de cabeza como hasOwnProperty, no poder obtener la longitud fácilmente (Object.keys (obj) .length) y así sucesivamente. Los objetos no están destinados a ser utilizados puramente como mapas hash, sino también como objetos dinámicos y 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 altibajos variables (rendimiento, terso, portátil, extensible, etc.). Los objetos son un poco extraños al ser el núcleo del lenguaje, por lo que tienes muchos métodos estáticos para trabajar con ellos.
Además de la ventaja de los Mapas, la preservación de los tipos de clave, así como la capacidad de admitir objetos como las claves, están aislados de los efectos secundarios que tienen 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 extender fácilmente con funciones de proxy. Actualmente, los objetos tienen una clase Proxy; sin embargo, el rendimiento y el uso de la memoria son severos, de hecho, se crea un proxy propio que parece que Map for Objects se desempeña mejor que Proxy.
Una desventaja importante para los mapas es que no son compatibles con JSON directamente. El análisis es posible pero tiene varios problemas:
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 clave 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á utilizando Mapas, pero tendrá problemas cuando esté mezclando tipos o utilizando valores no escalables como claves (no es que JSON sea perfecto con ese tipo de problema como es, referencia circular de objetos de IE). No lo he probado, pero lo más probable es que afecte gravemente el rendimiento en comparación con la de String.
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 problema con los tipos no escalares en los que tiene que lidiar con cosas como PHP combina Array / Map with Object utilizando A / M para propiedades y JS fusiona Map / Object con Array extendiendo M / O. La fusión de tipos complejos es la ruina del diablo de los lenguajes de script de alto nivel.
Hasta ahora, estos son en gran parte los problemas relacionados con la implementación, pero el rendimiento de las operaciones básicas también es importante. El rendimiento también es complejo porque depende del motor y el uso. Tomar 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, ya que las mías examinan 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
Claramente, Chrome tiene una gran ventaja al obtener y actualizar, pero el rendimiento de eliminación es horrible. Los mapas utilizan una pequeña cantidad de memoria en este caso (sobrecarga) pero con un solo objeto / mapa 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 para los objetos.
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 causa 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 rápido punto de referencia de esto, pero no exhaustivo (configuración / obtención), el cual funciona mejor con un pequeño número de teclas 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 el Objeto 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 de perfiles de uso de memoria y gastos generales. Me sorprendió ver que en Chrome parece que los mapas con una tecla usan 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 usó 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. Hice esta prueba varias veces y el mapa / objeto tiene más o menos cuello y cuello 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.
Esto, por supuesto, no incluye las opciones individuales que podrían variar enormemente. No recomendaría micro-optimización con estas cifras. Lo que puede sacar de esto es que, como regla general, considere los mapas con más fuerza para los almacenes de valores clave muy grandes y los objetos para los 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 hacer un perfil, es importante tener en cuenta que a veces las cosas que uno no pensaría que serían lentas al mirarlas pueden ser increíblemente lentas debido a las peculiaridades del motor, como se ve en el caso de eliminación de clave de objeto.
Los objetos pueden comportarse como diccionarios porque Javasript se escribe dinámicamente (y hasta ahora no ha habido una buena alternativa), pero realmente no están destinados a ser.
La nueva funcionalidad de Map()
es mucho más agradable porque tiene los métodos esperados de get/set/has/delete
que se esperaría, al mismo tiempo que acepta cualquier tipo para las teclas en lugar de solo cadenas. Es más fácil de usar cuando se realiza una iteración, y no tiene casos de borde con prototipos y otras propiedades que se muestran. También es muy rápido y sigue aumentando a medida que los motores mejoran. Para el 99% de los escenarios, solo debes usar un Map()
.
Sin embargo, si solo está usando claves basadas en cadenas y necesita un rendimiento de lectura máximo, entonces los objetos pueden ayudar. El detalle es que (casi todos) los motores de javascript compilan objetos en clases de C ++ en segundo plano. El esquema generalmente busca estos tipos, lo que significa que cuando crea un nuevo objeto con las mismas propiedades exactas que un objeto existente, el motor reutilizará una clase de fondo existente. Del mismo modo, la ruta de acceso para las propiedades en estas clases de respaldo está muy optimizada y es mucho más rápida que la redirección del método y la búsqueda 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 se vuelven a compilar sobre la marcha, por lo que pierde rendimiento cuando usa un objeto como un diccionario con mucha creación y eliminación de claves, pero lee y La asignación de una clave existente es muy rápida.
Entonces, si estás haciendo muchas lecturas, mira el object
como un diccionario especializado de alto rendimiento, pero para todo lo demás, usa un Map()
.
No creo que los siguientes puntos hayan sido mencionados en las respuestas hasta ahora, y pensé que valdría la pena mencionar.
Los mapas pueden ser más grandes
En Chrome puedo obtener 16.7 millones de pares clave / valor con Map
vs. 11.1 millones con un objeto regular. Casi exactamente 50% más pares con un Map
. Ambos consumen 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 con Chrome ( Edición : Sí, intenta llenar 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ú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 tienen algunas propiedades / claves ya
Este me ha tropezado antes. Los objetos regulares tienen toString
, constructor
, valueOf
, hasOwnProperty
, isPrototypeOf
y un montón de otras propiedades preexistentes. Esto puede no ser un gran problema para la mayoría de los casos de uso, pero me ha causado problemas antes.
Los mapas pueden ser más lentos:
Debido a la .get
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 y simple para algunas tareas.
Según mozilla:
Un objeto de mapa 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 le permiten establecer claves a 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 los objetos y los mapas que mejoran el uso de un mapa.
Un objeto tiene un prototipo, por lo que hay claves predeterminadas en el mapa. Sin embargo, esto se puede omitir usando map = Object.create (null). 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 no se conocen hasta el tiempo de ejecución, y cuando todas las claves son del mismo tipo y todos los valores son del mismo tipo.
Usar objetos cuando hay lógica que opera sobre 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 querido durante mucho tiempo, en parte porque garantiza el mismo rendimiento en todos los navegadores. Así que para mí eso es grande.
El myMap.has(key)
será especialmente útil, y también la propiedad myMap.size
.