web-dev-qa-db-fra.com

Carte vs objet en JavaScript

Je viens de découvrir chromestatus.com et, après avoir perdu plusieurs heures de ma journée, j'ai trouvé cette entrée de fonctionnalité :

Carte: les objets de carte sont de simples cartes clé/valeur.

Cela m'a confondu. Les objets JavaScript standard sont des dictionnaires. En quoi une Map est-elle différente d'un dictionnaire? Conceptuellement, ils sont identiques (selon Quelle est la différence entre une carte et un dictionnaire? )

Les références de chromestatus dans la documentation n’aident pas non plus:

Les objets de carte sont des collections de paires clé/valeur dans lesquelles les clés et les valeurs peuvent être des valeurs arbitraires du langage ECMAScript. Une valeur de clé distincte ne peut apparaître que dans une paire clé/valeur de la collection de la carte. Valeurs de clé distinctes, différenciées à l'aide d'un algorithme de comparaison sélectionné lors de la création de la carte.

Un objet Map peut itérer ses éléments dans l'ordre d'insertion. Les objets de carte doivent être implémentés à l'aide de tables de hachage ou d'autres mécanismes fournissant en moyenne des temps d'accès sous-linéaires pour le nombre d'éléments de la collection. Les structures de données utilisées dans cette spécification d'objets Map sont uniquement destinées à décrire la sémantique observable requise des objets Map. Il ne s'agit pas d'un modèle de mise en œuvre viable.

… Sonne toujours comme un objet pour moi, si clairement que j'ai raté quelque chose.

Pourquoi JavaScript obtient-il un objet (bien pris en charge) Map? Qu'est ce que ça fait?

226
Dave

Selon mozilla:

Un objet Map peut itérer ses éléments dans l'ordre d'insertion - une boucle for..of retournera un tableau de [clé, valeur] pour chaque itération.

et

Les objets sont similaires aux cartes, car ils vous permettent de définir des valeurs pour les clés, de les récupérer, de les supprimer et de détecter si quelque chose est stocké dans une clé. Pour cette raison, les objets ont été utilisés historiquement comme cartes; Cependant, il existe des différences importantes entre les objets et les cartes qui facilitent l'utilisation d'une carte.

Un objet a un prototype, il y a donc des clés par défaut dans la carte. Cependant, ceci peut être contourné en utilisant map = Object.create (null). Les clés d'un objet sont des chaînes, où elles peuvent être n'importe quelle valeur pour une carte. Vous pouvez facilement obtenir la taille d’une carte tout en devant suivre manuellement la taille d’un objet.

Utilisez des cartes sur des objets lorsque les clés sont inconnues jusqu'au moment de l'exécution, lorsque toutes les clés sont du même type et que toutes les valeurs sont du même type.

Utilisez des objets lorsqu'il existe une logique qui opère sur des éléments individuels.

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

L'itérabilité en ordre est une fonctionnalité recherchée depuis longtemps par les développeurs, notamment parce qu'elle garantit les mêmes performances dans tous les navigateurs. Donc pour moi c'est un gros.

La méthode myMap.has(key) sera particulièrement pratique, ainsi que la propriété myMap.size.

216
user2625787

La principale différence est que les objets ne prennent en charge que les clés de chaîne, car Maps prend en charge plus ou moins tout type de clé.

Si je fais obj [123] = true puis Object.keys (obj) alors j'aurai ["123"] plutôt que [123]. Une carte conserverait le type de clé et renverrait [123], ce qui est très bien. Les cartes vous permettent également d'utiliser des objets comme clés. Traditionnellement, il fallait attribuer aux objets une sorte d'identifiant unique pour les hacher (je ne pense pas avoir jamais rien vu de tel que getObjectId dans JS dans le cadre de la norme). Les cartes garantissent également le maintien de l'ordre; elles sont donc toutes meilleures pour la conservation et peuvent parfois vous éviter de faire plusieurs sortes.

Entre cartes et objets dans la pratique, il y a plusieurs avantages et inconvénients. Les objets présentent à la fois des avantages et des inconvénients, car ils sont très étroitement intégrés au cœur de JS, ce qui les différencie nettement de Map Map au-delà de la différence de support clé.

Un avantage immédiat est que vous disposez d'un support syntaxique pour les objets facilitant l'accès aux éléments. Vous avez également un support direct pour cela avec JSON. Lorsqu'il est utilisé comme un hachage, il est ennuyeux d'obtenir un objet sans aucune propriété. Par défaut, si vous souhaitez utiliser des objets en tant que table de hachage, ils seront pollués et vous devrez souvent appeler hasOwnProperty lors de l'accès aux propriétés. Vous pouvez voir ici comment les objets sont pollués par défaut et comment créer des objets non pollués, espérons-le, à utiliser comme hachages:

({}).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 pollution sur les objets ne rend pas seulement le code plus ennuyeux, plus lent, mais peut aussi avoir des conséquences potentielles pour la sécurité.

Les objets ne sont pas de simples tables de hachage mais tentent de faire plus. Vous avez des maux de tête comme hasOwnProperty, vous ne pouvez pas obtenir facilement la longueur (Object.keys (obj) .length) et ainsi de suite. Les objets ne sont pas destinés à être utilisés uniquement comme des cartes de hachage, mais également comme des objets extensibles dynamiques. Par conséquent, lorsque vous les utilisez comme tables de hachage pures, des problèmes se posent.

Comparaison/Liste des différentes opérations courantes:

    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();

Il existe quelques autres options, approches, méthodologies, etc. avec des hauts et des bas variables (performances, lacérations, portable, extensible, etc.). Les objets sont un peu étranges étant au cœur du langage, vous avez donc beaucoup de méthodes statiques pour travailler avec eux.

Outre l'avantage de préserver les types de clé des cartes, vous pouvez également les isoler des objets tels que les objets. Une carte est un pur hash, il n'y a pas de confusion à vouloir être un objet en même temps. Les cartes peuvent également être facilement étendues avec des fonctions de proxy. Les objets ont actuellement une classe Proxy, mais les performances et l'utilisation de la mémoire sont médiocres. En fait, créer votre propre proxy qui ressemble à Map for Objects est actuellement plus performant que Proxy.

Un inconvénient majeur de Maps est qu’elles ne sont pas directement prises en charge par JSON. L’analyse est possible mais a plusieurs problèmes:

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;
});

Ce qui précède introduira un problème de performances sérieux et ne prendra également en charge aucune clé de chaîne. L’encodage JSON est encore plus difficile et problématique (c’est l’une des nombreuses approches):

// 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())
    });
};

Ce n’est pas si grave si vous utilisez purement Maps mais que vous rencontrez des problèmes lorsque vous mélangez des types ou utilisez des valeurs non scalaires comme clés (pas que JSON soit parfait avec ce genre de problème, IE référence d'objet circulaire). Je ne l'ai pas testé, mais il y a de fortes chances que cela nuise gravement aux performances par rapport à une rigidité.

Les autres langages de script n'ont souvent pas ces problèmes, car ils ont des types non scalaires explicites pour Map, Object et Array. Le développement Web est souvent un problème avec les types non scalaires où vous devez faire face à des choses comme PHP fusionne Array/Map avec Object en utilisant A/M pour les propriétés et JS fusionne Map/Object avec Array qui étend M/O . La fusion de types complexes constitue le fléau du diable des langages de script de haut niveau.

Jusqu'à présent, ce sont principalement des problèmes d'implémentation, mais la performance des opérations de base est également importante. Les performances sont également complexes car elles dépendent du moteur et de l’utilisation. Prenez mes tests avec un grain de sel car je ne peux exclure aucune erreur (je dois me précipiter). Vous devez également exécuter vos propres tests pour confirmer, car le mien n’examine que des scénarios simples très spécifiques pour donner une indication approximative. Selon les tests effectués dans Chrome pour les objets/cartes très volumineux, les performances des objets sont moins bonnes en raison de la suppression, qui est apparemment proportionnelle au nombre de clés plutôt qu'à 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 a clairement un avantage certain à obtenir et à mettre à jour, mais les performances de suppression sont horribles. Les cartes utilisent très peu de mémoire dans ce cas (surcharge), mais avec un seul objet/carte testé avec des millions de clés, l'impact de la surcharge pour les cartes n'est pas bien exprimé. Avec la gestion de la mémoire, les objets semblent également se libérer plus tôt si je lis correctement le profil, ce qui pourrait constituer un avantage pour les objets.

Dans FireFox, pour ce critère particulier, l'histoire est différente:

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

Je dois immédiatement signaler que, dans ce cas-test particulier, la suppression d'objets dans FireFox ne pose aucun problème. Toutefois, dans d'autres cas, elle a causé des problèmes, notamment lorsqu'il y a plusieurs clés, comme dans Chrome. Les cartes sont clairement supérieures dans FireFox pour les grandes collections.

Cependant, ce n'est pas la fin de l'histoire, qu'en est-il de nombreux petits objets ou cartes? J'ai fait un test rapide de ce test, mais non exhaustif (réglage/obtention), dont les performances sont optimales avec un petit nombre de clés dans les opérations ci-dessus. Ce test concerne plus la mémoire et l’initialisation.

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

Encore une fois, ces chiffres varient, mais l’objet a une bonne avance. Dans certains cas, l’avance d’objets sur les cartes est extrême (environ 10 fois supérieure), mais en moyenne elle était environ 2 à 3 fois meilleure. Il semble que les pics de performances extrêmes puissent fonctionner dans les deux sens. Je n’ai testé cela que dans Chrome et lors de la création de profils d’utilisation et de temps système de la mémoire. J'ai été assez surpris de constater que, dans Chrome, il semble que les cartes avec une clé utilisent environ 30 fois plus de mémoire que les objets avec une clé.

Pour tester de nombreux petits objets avec toutes les opérations ci-dessus (4 clés):

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

En termes d’allocation de mémoire, ils se comportaient de la même manière en termes de libération/GC, mais Map utilisait 5 fois plus de mémoire. Ce test a utilisé 4 clés. Comme dans le dernier test, je n’ai défini qu’une clé, ce qui expliquerait la réduction de la surcharge de mémoire. J'ai couru ce test plusieurs fois et Map/Object est plus ou moins au coude à coude pour Chrome en termes de vitesse globale. Dans FireFox pour les petits objets, les performances globales sont nettement meilleures.

Ceci n'inclut bien sûr pas les options individuelles qui pourraient varier énormément. Je ne conseillerais pas la micro-optimisation avec ces chiffres. Ce que vous pouvez en déduire, c’est qu’en règle générale, considérons Cartes plus fortement pour les très grands magasins de valeurs de clé et les objets pour les magasins de petite valeur.

Au-delà de cela, la meilleure stratégie avec ces deux éléments est de le mettre en œuvre et de le faire fonctionner en premier. Lors du profilage, il est important de garder à l'esprit que, parfois, des choses que vous ne penseriez pas être lentes peuvent être incroyablement lentes en raison des bizarreries du moteur, comme le montre le cas de suppression de clé d'objet.

86
jgmjgm

Je ne pense pas que les points suivants aient été mentionnés dans les réponses jusqu’à présent, et j’ai pensé qu’ils mériteraient d’être mentionnés.


Les cartes peuvent être plus grandes

Dans chrome je peux obtenir 16,7 millions de paires clé/valeur avec Map vs. 11,1 millions avec un objet régulier. Presque exactement 50% de paires en plus avec un Map. Ils utilisent tous les deux environ 2 Go de mémoire avant de tomber en panne, et je pense donc que la limitation de la mémoire par chrome ( Edit est nécessaire. ): Oui, essayez de remplir 2 Maps et vous n’obtenez que 8,3 millions de paires chacune avant qu’elle ne tombe en panne). Vous pouvez le tester vous-même avec ce code (exécutez-les séparément et pas en même temps, évidemment):

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") }
}

Les objets ont déjà des propriétés/clés

Celui-ci m'a déjà fait trébucher. Les objets normaux ont toString, constructor, valueOf, hasOwnProperty, isPrototypeOf et plusieurs autres propriétés préexistantes. Ce n'est peut-être pas un gros problème pour la plupart des cas d'utilisation, mais cela m'a déjà posé problème auparavant.

Les cartes peuvent être plus lentes:

En raison de la surcharge de l'appel de la fonction .get et du manque d'optimisation interne, Map peut être considérablement plus lent qu'un ancien objet JavaScript ordinaire pour certaines tâches.

19
user993683

En plus des autres réponses, j'ai constaté que les cartes sont plus difficiles à manier et plus compliquées à utiliser que les objets.

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

Ceci est important, car un code plus court est plus rapide à lire, plus directement expressif et meilleur gardé dans la tête du programmeur .

Autre aspect: comme set () renvoie la carte, pas la valeur, il est impossible de chaîner les affectations.

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

Le débogage de cartes est également plus douloureux. En dessous, vous ne pouvez pas voir quelles clés sont sur la carte. Il faudrait écrire du code pour le faire.

Good luck evaluating a Map Iterator

Les objets peuvent être évalués par n'importe quel IDE:

WebStorm evaluating an object

6
Dan Dascalescu

Sommaire:

  • Object: structure de données dans laquelle des données sont stockées sous forme de paires clé-valeur. Dans un objet, la clé doit être un nombre, une chaîne ou un symbole. La valeur peut être n'importe quoi ainsi que d'autres objets, fonctions, etc. Un objet est une structure de données non ordonnée , c'est-à-dire que la séquence d'insertion des paires clé-valeur n'est pas mémorisée.
  • ES6 Map: structure de données dans laquelle des données sont stockées sous forme de paires clé-valeur. Dans lequel une clé unique mappe sur une valeur . La clé et la valeur peuvent être dans n'importe quel type de données . Une carte est une structure de données itérable, cela signifie que la séquence d'insertion est mémorisée et que nous pouvons accéder aux éléments, par exemple. une boucle _for..of_

Principales différences:

  • Un Map est ordonné et éditable, alors qu'un objet n'est ni ordonné ni itérable

  • Nous pouvons placer n'importe quel type de données sous la forme d'une clé Map, alors que les objets ne peuvent avoir qu'un nombre, une chaîne ou un symbole en tant que clé.

  • Un Map hérite de _Map.prototype_. Cela offre toutes sortes de fonctions et de propriétés utilitaires, ce qui facilite grandement l'utilisation des objets Map.

Exemple:

objet:

_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)_

Carte:

_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 () {}_

source: MDN

5

Les objets peuvent se comporter comme des dictionnaires car Javascript est typé de manière dynamique, mais ils ne sont pas censés l'être.

La nouvelle fonctionnalité Map() est bien plus agréable car elle utilise des méthodes get/set/has/delete normales, accepte tout type de clé plutôt que de simples chaînes, est plus facile à utiliser lors de l'itération et ne comporte pas de cas Edge avec prototypes et d'autres propriétés se présentant. C'est aussi très rapide et continue à aller plus vite à mesure que les moteurs s'améliorent. 99% du temps, vous devriez simplement utiliser un Map().

Toutefois, si vous n'utilisez que des clés basées sur des chaînes et que vous avez besoin d'une performance de lecture maximale, les objets peuvent constituer un meilleur choix. Le détail est que (presque tous) les moteurs javascript compilent les objets en classes C++ en arrière-plan. Ces types sont mis en cache et réutilisés par leur "contour". Ainsi, lorsque vous créez un nouvel objet avec les mêmes propriétés exactes, le moteur réutilisera une classe d'arrière-plan existante. Le chemin d'accès aux propriétés de ces classes est très optimisé et beaucoup plus rapide que la recherche d'un Map().

L'ajout ou la suppression d'une propriété entraîne la recompilation de la classe de support en cache. C'est pourquoi il est très lent d'utiliser un objet comme dictionnaire avec beaucoup de suppressions et de suppressions de clés, mais la lecture et l'attribution de clés existantes sans modifier l'objet sont très rapides.

Ainsi, si vous avez une charge de travail écriture-lecture-lourde avec des clés de chaîne, utilisez un object comme dictionnaire spécialisé hautes performances, mais pour tout le reste, utilisez un Map().

5
Mani Gandham

En plus d'être itérables dans un ordre bien défini et de pouvoir utiliser des valeurs arbitraires en tant que clés (sauf -0), les mappes peuvent être utiles pour les raisons suivantes:

  • La spécification impose que les opérations de carte soient sous-linéaires en moyenne.

    Toute implémentation d'objet non stupide utilisera une table de hachage ou similaire, donc les recherches de propriétés seront probablement constantes en moyenne. Les objets pourraient alors être encore plus rapides que les cartes. Mais cela n'est pas requis par la spécification.

  • Les objets peuvent avoir de mauvais comportements inattendus.

    Par exemple, supposons que vous n'ayez défini aucune propriété foo sur un objet nouvellement créé obj, vous attendez donc que obj.foo renvoie non défini. Mais foo pourrait être une propriété intégrée héritée de Object.prototype. Ou vous essayez de créer obj.foo en utilisant une affectation, mais un opérateur de configuration dans Object.prototype s'exécute au lieu de stocker votre valeur.

    Les cartes empêchent ce genre de choses. Eh bien, à moins que certains scripts ne gâchent Map.prototype. Et Object.create(null) fonctionnerait aussi, mais vous perdriez alors la syntaxe d'initialisation d'objet simple.

3
Oriol

Ces deux conseils peuvent vous aider à décider d’utiliser une carte ou un objet:

  • Utilisez des cartes sur des objets lorsque les clés sont inconnues jusqu'au moment de l'exécution, lorsque toutes les clés sont du même type et que toutes les valeurs sont du même type.

  • Utilisez des mappes au cas où il serait nécessaire de stocker des valeurs primitives sous forme de clés, car object traite chaque clé comme une chaîne, soit une valeur numérique, une valeur booléenne ou toute autre valeur primitive.

  • Utilisez des objets lorsqu'il existe une logique qui opère sur des éléments individuels.

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

0
Cakedy