Saltar al contenido

Mapa vs objeto en JavaScript

Solución:

Según mozilla:

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

y

Los objetos son similares a los mapas en que ambos le permiten establecer claves para valores, recuperar esos valores, eliminar claves y detectar si algo está almacenado en una clave. Debido a esto, los Objetos se han utilizado históricamente como Mapas; sin embargo, existen diferencias importantes entre los objetos y los mapas que hacen que el uso de 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 (null). Las claves de un objeto son cadenas, donde pueden tener 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.

Utilice mapas sobre objetos cuando las claves sean desconocidas hasta el momento de la ejecución, y cuando todas las claves sean del mismo tipo y todos los valores sean del mismo tipo.

Utilice objetos cuando haya una lógica que opere 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 deseado durante mucho tiempo, en parte porque garantiza el mismo rendimiento en todos los navegadores. Así que para mí es uno de los grandes.

los myMap.has(key) será especialmente útil, y también el myMap.size propiedad.

La diferencia clave es que los objetos solo admiten teclas de cadena y símbolo, mientras que los mapas admiten más o menos cualquier tipo de clave.

Si lo hago obj[123] = true y luego Object.keys(obj) entonces obtendré ["123"] en vez de [123]. Un mapa conservaría el tipo de clave y devolvería [123] Lo cual es genial. Los mapas también le permiten utilizar objetos como claves. Tradicionalmente, para hacer esto, tendrías que darles a los objetos algún tipo de identificador único para codificarlos (no creo que 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 los mapas y los objetos en la práctica existen varios pros y contras. Los objetos obtienen ventajas y desventajas al estar estrechamente integrados en el núcleo de JavaScript, lo que los distingue significativamente de Map más allá de la diferencia en el soporte clave.

Una ventaja inmediata es que tiene soporte sintáctico para 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 llamar hasOwnProperty sobre ellos al acceder a las propiedades. Puede ver aquí cómo los objetos están contaminados de forma predeterminada y cómo crear objetos con suerte 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 de 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, sino que intentan hacer más. Tienes dolores de cabeza como hasOwnProperty, no poder obtener la longitud fácilmente (Object.keys(obj).length) etcétera. Los objetos no están pensados ​​para usarse únicamente como mapas hash, sino también como Objetos dinámicos extensibles y, por lo tanto, 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 fundamentales para el lenguaje, por lo que tiene muchos métodos estáticos para trabajar con ellos.

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

Una desventaja sustancial de Maps es que no son compatibles con JSON directamente. El análisis es posible pero tiene varios bloqueos:

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 presentará un gran impacto en el rendimiento 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 está usando únicamente Maps, pero tendrá problemas cuando esté mezclando tipos o usando valores no escalares como claves (no es que JSON sea perfecto con ese tipo de problema tal como es, referencia de objeto circular de IE). No lo he probado, pero es probable que perjudique gravemente el rendimiento en comparación con el stringify.

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

Hasta ahora, estos son en gran parte 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. 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, 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 de alguna manera proporcional al número de claves 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 para obtener y actualizar, pero el rendimiento de eliminación es terrible. Los mapas usan una pequeña cantidad más de memoria en este caso (sobrecarga) pero con solo un Objeto / Mapa siendo probado con millones de claves, el impacto de sobrecarga para mapas no se expresa bien. Con la administración de memoria, los objetos también parecen liberarse antes si leo el perfil correctamente, lo que podría ser un beneficio a favor de los objetos.

En Firefox para este punto de referencia en particular, la historia es 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, la eliminación de 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 colecciones grandes.

Sin embargo, este no es el final de la historia, ¿qué pasa con muchos objetos pequeños o mapas? He hecho una evaluación comparativa rápida de esto, pero no una exhaustiva (configuración / obtención) de la cual funciona mejor con una pequeña cantidad de claves en las operaciones anteriores. Esta prueba trata más sobre la memoria y la 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 de 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 la sobrecarga. Me sorprendió bastante ver que en Chrome parece que los mapas con una tecla usan alrededor de 30 veces más memoria que los objetos con una sola 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 claves donde, como en la última prueba, solo configuré una clave para que esto explicara la reducción en la sobrecarga de memoria. Hice esta prueba varias veces y Map / Object 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 clara ventaja de rendimiento sobre los mapas en general.

Por supuesto, esto no incluye las opciones individuales que podrían variar enormemente. No recomendaría realizar una microoptimización con estas cifras. Lo que puede obtener de esto es que, como regla general, considere Maps con más fuerza para almacenes de valores clave muy grandes y 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 perfiles, 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.

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


Los mapas pueden ser más grandes

En cromo puedo conseguir 16,7 millones de pares clave / valor con Map vs. 11,1 millones con un objeto regular. Casi exactamente un 50% más de pares con un Map. Ambos ocupan aproximadamente 2 GB de memoria antes de fallar, por lo que creo que puede tener que ver con la limitación de memoria por Chrome (Editar: Sí, intente llenar 2 Maps y solo llega a 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 ya tienen algunas propiedades / claves

Este me ha hecho tropezar antes. Los objetos regulares tienen toString, constructor, valueOf, hasOwnProperty, isPrototypeOf y un montón de otras propiedades preexistentes. Puede que esto no sea 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 la sobrecarga de llamadas de función y la falta de optimización interna, Map puede ser considerablemente más lento que un simple objeto JavaScript antiguo para algunas tareas.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *