Map vs Object en JavaScript

290

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 dérouté. Les objets JavaScript normaux sont des dictionnaires, alors en quoi Mapdiffère-t-il d'un dictionnaire? Conceptuellement, ils sont identiques (selon Quelle est la différence entre une carte et un dictionnaire? )

La documentation des références chromestatus n'aide pas non plus:

Les objets de carte sont des collections de paires clé / valeur où les clés et les valeurs peuvent être des valeurs de langage ECMAScript arbitraires. Une valeur de clé distincte ne peut se produire que dans une seule paire clé / valeur dans la collection de la carte. Valeurs clés distinctes telles que 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. L'objet de carte doit être implémenté à l'aide de tables de hachage ou d'autres mécanismes qui, en moyenne, fournissent des temps d'accès qui sont sous-linéaires par rapport au nombre d'éléments dans 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, donc il est clair que j'ai raté quelque chose.

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

Dave
la source

Réponses:

286

Selon mozilla:

Un objet Map peut parcourir 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 dans la mesure où ils vous permettent de définir des clés pour des valeurs, de récupérer ces valeurs, de supprimer des clés et de détecter si quelque chose est stocké sur une clé. Pour cette raison, les objets ont été utilisés comme cartes historiquement; cependant, il existe des différences importantes entre les objets et les cartes qui rendent l'utilisation d'une carte meilleure.

Un objet a un prototype, il y a donc des clés par défaut dans la carte. Cependant, cela 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 gardant une trace manuelle de la taille d'un objet.

Utilisez des mappages sur des objets lorsque les clés sont inconnues jusqu'à l'exécution, et lorsque toutes les clés sont du même type et 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é dans l'ordre est une fonctionnalité recherchée depuis longtemps par les développeurs, en partie parce qu'elle garantit les mêmes performances dans tous les navigateurs. Donc pour moi, c'est un gros problème.

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


la source
13
Un inconvénient, sans doute, est qu'une carte nécessite plus de mémoire (dans le même ordre de grandeur, cependant) afin de maintenir l'ordre d'insertion.
John Kurlak
4
Les cartes ont d'autres fonctionnalités en plus de l'ordre qui ont été mentionnées ici (utilisation de n'importe quel objet comme clé, séparation des clés et des accessoires, etc.), mais FWIW dans certains cas, l'ordre d'itération des propriétés des objets simples est défini par ES2015. Voir stackoverflow.com/a/32149345 .
JMM
2
Je n'ai pas compris le sens, quand vous dites, Un objet a un prototype, donc il y a des clés par défaut dans la carte. Cependant, cela peut être contourné à l'aide demap = Object.create(null) . Quelles sont les clés par défaut? Comment les clés sont-elles liées Object.prototype?
suréchange
4
Mes tests dans Chrome ont montré que les cartes n'utilisaient pas beaucoup plus de mémoire pour maintenir l'ordre. Je pense qu'il y avait 0,1 Ko de plus pour un million de clés et je ne pense pas que c'était pour maintenir l'ordre. Cependant, cet ~ 0,1 Ko semble être une surcharge constante. Si vous créez plutôt un million de cartes avec une seule clé et que vous comparez, il est beaucoup plus grand que l'objet.
jgmjgm
2
@luxon vous créez un objet là-bas. La spécification ES6 nécessite que l' newopérateur soit utilisé avec le Mapsymbole, c'est- new Mapà- dire qu'il crée un objet cartographique. var a = {}est un raccourci pour (ce qui signifie équivalent à)var a = Object.create(Object.prototype)
dudewad
104

La principale différence est que les objets ne prennent en charge que les clés de chaîne alors que les cartes prennent en charge plus ou moins n'importe quel type de clé.

Si je le fais obj[123] = trueet puis Object.keys(obj)je vais ["123"]plutôt [123]. Une carte conserverait le type de la clé et retournerait [123]ce qui est génial. Les cartes vous permettent également d'utiliser des objets comme clés. Traditionnellement, pour ce faire, vous devez donner aux objets une sorte d'identifiant unique pour les hacher (je ne pense pas avoir jamais vu quelque chose comme getObjectIdJS dans le cadre de la norme). Les cartes garantissent également la préservation de l'ordre, elles sont donc toutes meilleures pour la conservation et peuvent parfois vous éviter d'avoir à faire quelques sortes.

Entre les cartes et les objets dans la pratique, il y a plusieurs avantages et inconvénients. Les objets gagnent à la fois en avantages et en inconvénients en étant très étroitement intégrés au cœur de JavaScript, ce qui les distingue de Map de manière significative au-delà de la différence de prise en charge des clés.

Un avantage immédiat est que vous disposez d'un support syntaxique pour les objets, ce qui facilite l'accès aux éléments. Vous avez également un support direct pour cela avec JSON. Lorsqu'il est utilisé comme hachage, il est ennuyeux d'obtenir un objet sans aucune propriété. Par défaut, si vous souhaitez utiliser des objets comme table de hachage, ils seront pollués et vous devrez souvent les appeler hasOwnPropertylors de l'accès aux propriétés. Vous pouvez voir ici comment les objets par défaut sont pollués et comment créer des objets non pollués, espérons-le, pour les 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 des objets n'est pas seulement quelque chose qui rend le code plus ennuyeux, plus lent, etc., mais peut également avoir des conséquences potentielles pour la sécurité.

Les objets ne sont pas des tables de hachage pures mais essaient d'en faire plus. Vous avez des maux de tête comme le fait de hasOwnPropertyne pas pouvoir obtenir la longueur facilement (Object.keys(obj).length ) et ainsi de suite. Les objets ne sont pas destinés à être purement utilisés comme des cartes de hachage mais également comme des objets extensibles dynamiques et donc lorsque vous les utilisez en tant que tables de hachage pures, des problèmes surviennent.

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, laconique, 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 la préservation des types de clés par Maps ainsi que la possibilité de prendre en charge des éléments tels que des objets, ils sont isolés des effets secondaires des objets. Une carte est un hachage pur, il n'y a aucune confusion à essayer d'être un objet en même temps. Les cartes peuvent également être facilement étendues avec des fonctions proxy. Les objets ont actuellement une classe proxy, mais les performances et l'utilisation de la mémoire sont sombres, en fait, la création de votre propre proxy qui ressemble à Map for Objects fonctionne actuellement mieux que Proxy.

Un inconvénient majeur pour Maps est qu'elles ne sont pas prises en charge directement avec JSON. L'analyse est possible mais comporte plusieurs blocages:

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 sérieux impact sur les performances 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 mal si vous utilisez uniquement Maps, mais 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 type de problème tel qu'il est, référence d'objet circulaire IE). Je ne l'ai pas testé, mais il est probable que cela nuira gravement aux performances par rapport à la chaîne.

D'autres langages de script n'ont souvent pas de tels 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 traiter 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 étendant M / O. La fusion de types complexes est le fléau du diable des langages de script de haut niveau.

Jusqu'à présent, il s'agit principalement de problèmes liés à la mise en œuvre, mais les performances des opérations de base sont également importantes. Les performances sont également complexes car elles dépendent du moteur et de l'utilisation. Faites mes tests avec un grain de sel car je ne peux exclure aucune erreur (je dois précipiter cela). Vous devez également exécuter vos propres tests pour confirmer que le mien n'examine que des scénarios simples très spécifiques pour donner une indication approximative uniquement. Selon les tests dans Chrome pour les très gros objets / cartes, les performances des objets sont pires 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 fort avantage à obtenir et à mettre à jour, mais les performances de suppression sont horribles. Les cartes utilisent dans ce cas une quantité minime de mémoire supplémentaire (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 être libérés plus tôt si je lis correctement le profil, ce qui pourrait être un avantage en faveur des objets.

Dans Firefox pour cette référence particulière, c'est une autre histoire:

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 souligner que dans ce cas-ci, la suppression des objets dans Firefox ne pose aucun problème, mais dans d'autres cas-là, cela a causé des problèmes, surtout quand il y a beaucoup de clés, tout comme dans Chrome. Les cartes sont clairement supérieures dans Firefox pour les grandes collections.

Mais ce n'est pas la fin de l'histoire, qu'en est-il de nombreux petits objets ou cartes? J'ai fait un benchmark rapide de cela mais pas exhaustif (réglage / obtention) qui fonctionne mieux avec un petit nombre de clés dans les opérations ci-dessus. Ce test concerne davantage la mémoire et l'initialisation.

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

Encore une fois, ces chiffres varient, mais en gros Object a une bonne avance. Dans certains cas, l'avance des objets sur les cartes est extrême (~ 10 fois meilleure) 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 sa création pour profiler l'utilisation et la surcharge de la mémoire. J'ai été assez surpris de voir que dans Chrome, il semble que les cartes avec une seule clé utilisent environ 30 fois plus de mémoire que les objets avec une seule clé.

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

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

En termes d'allocation de mémoire, ceux-ci se sont comportés de la même manière en termes de libération / GC, mais Map a utilisé 5 fois plus de mémoire. Ce test a utilisé 4 touches où, comme dans le dernier test, je n'ai défini qu'une seule clé, ce qui expliquerait la réduction de la surcharge de la mémoire. J'ai effectué ce test plusieurs fois et Map / Object sont globalement au coude à coude pour Chrome en termes de vitesse globale. Dans Firefox pour les petits objets, il y a un avantage certain en termes de performances par rapport aux cartes en général.

Bien sûr, cela n'inclut pas les options individuelles qui peuvent varier énormément. Je ne conseillerais pas la micro-optimisation avec ces chiffres. Ce que vous pouvez en tirer, c'est qu'en règle générale, considérez Maps plus fortement pour les très grands magasins de valeurs clés et les objets pour les petits magasins de valeurs clés.

Au-delà de cela, la meilleure stratégie avec ces deux-là est de la mettre en œuvre et de la 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 en les regardant peuvent être incroyablement lentes en raison des bizarreries du moteur, comme on le voit avec le cas de suppression de clé d'objet.

jgmjgm
la source
Le manque de sérialisation a été une vraie douleur pour de nombreux développeurs. Regardez le vote positif de Comment puis-je conserver une carte ES6 dans le stockage local (ou ailleurs)? et comment JSON.stringifiez-vous une carte ES6? .
Franklin Yu
Le nombre est-il en millisecondes, octets ou nombre total d'objets?
StefansArya
Cela a pris ms (quelque chose a pris est court pour dire quelque chose utilisé, donc cela utilise du temps dans ce cas). Bien que ce soit un vieux test et je n'ai plus le code de référence. C'est probablement très différent maintenant. Le problème de suppression, par exemple, je pense, est résolu.
jgmjgm
27

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

En chrome je peux obtenir 16,7 millions de paires clé / valeur avec Mapcontre 11,1 millions avec un objet régulier. Presque exactement 50% de paires en plus avec a Map. Ils occupent tous les deux environ 2 Go de mémoire avant de tomber en panne, et je pense donc que cela peut être lié à la limitation de la mémoire par Chrome ( Edit : Oui, essayez de remplir 2 Mapset vous n'obtenez que 8,3 millions de paires avant de se bloquer). 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. Objets réguliers ont toString, constructor, valueOf, hasOwnProperty,isPrototypeOf et un tas d'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à causé des problèmes.

Les cartes peuvent être plus lentes:

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


la source
1
À votre avis, la sémantique l'emporte-t-elle sur les performances ici? Les cartes semblent parfaites si vous avez besoin d'un dictionnaire, mais il est difficile d'accepter une recherche plus lente. Une recherche rapide n'est-elle pas tout l'intérêt des dictionnaires?
user2954463
3
Je serais certainement aller avec des objets anciens simples si vous êtes bien avec 11 millions de paires clé / valeur et ne se soucient pas des clés pré-existantes comme toString, constructor, etc. (c. - clés sont très peu susceptibles d'entrer en collision avec eux). Ils sont plus faciles à travailler - par exemple, l'incrémentation l'est obj[i] = (obj[i] || 0) + 1, tandis qu'avec Mapelle, map.set(i, (map.get(i) || 0) + 1)ce n'est toujours pas trop mal, mais cela montre simplement comment les choses peuvent devenir inutilement désordonnées. Les cartes ont certainement leurs cas d'utilisation, mais souvent un simple objet fera l'affaire.
Notez que vous pouvez vous débarrasser de la valeur par défaut toString, les constructorpropriétés d'objet, (etc.) en écrivant au obj = Object.create(null)lieu de obj = {}.
17

Les objets peuvent se comporter comme des dictionnaires car Javascript est typé dynamiquement, vous permettant d'ajouter ou de supprimer des propriétés sur un objet à tout moment.

Mais la nouvelle Map()fonctionnalité est bien meilleure car:

  • Il fournit get, set, haset deleteméthodes.
  • Accepte tout type de clé au lieu de simplement des chaînes.
  • Fournit un itérateur pour une for-ofutilisation facile et maintient l'ordre des résultats.
  • N'a pas de cas de bord avec des prototypes et d'autres propriétés apparaissant pendant l'itération ou la copie.
  • Il prend en charge des millions d'articles.
  • Est très rapide et continue de s'accélérer à mesure que les moteurs javascript s'améliorent.

99% du temps, vous devez simplement utiliser a Map(). Cependant, si vous n'utilisez que des clés basées sur des chaînes et que vous avez besoin de performances de lecture maximales, les objets peuvent être un meilleur choix.

Le détail est que (presque tous) les moteurs javascript compilent les objets jusqu'aux classes C ++ en arrière-plan. Ces types sont mis en cache et réutilisés par leur "contour", donc 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 sauvegarde en cache, c'est pourquoi l'utilisation d'un objet comme dictionnaire avec de nombreux ajouts et suppressions de clés est très lente, mais les lectures et l'affectation de clés existantes sans modifier l'objet sont très rapides.

Donc, si vous avez une charge de travail lourde en lecture seule avec des clés de chaîne, utilisez un objectcomme un dictionnaire spécialisé haute performance, mais pour tout le reste, utilisez un Map().

Mani Gandham
la source
L'objet fournit get set has deleteégalement des fonctionnalités, etc., il n'est tout simplement pas aussi élégant (mais pas mal non plus). De quelle manière est-il Mapplus facile à utiliser pour itérer? Je ne suis pas sûr d'être d'accord.
Andrew
@Andrew, je parle des méthodes et les fonctionnalités sont également différentes en fonction de ce que vous utilisez et du résultat. L'itération est plus facile car le prototype et les propriétés natives n'apparaissent pas dans la boucle et utilisent un itérateur JS normal qui maintient le même ordre.
Mani Gandham
11

En plus des autres réponses, j'ai trouvé que les cartes sont plus difficiles à manier et à 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 mieux conservé dans la tête du programmeur .

Autre aspect: parce que 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 des cartes est également plus pénible. Ci-dessous, vous ne pouvez pas réellement voir quelles clés se trouvent sur la carte. Vous devez écrire du code pour ce faire.

Bonne chance pour évaluer un itérateur de carte

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

WebStorm évaluant un objet

Dan Dascalescu
la source
4
Compte tenu de tout cela, il semble que la carte soit une optimisation prématurée.
PRMan
10

Résumé:

  • Object: Une structure de données dans laquelle les données sont stockées sous forme de paires de valeurs clés. 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 de valeurs clés n'est pas mémorisée
  • ES6 Map: Une structure de données dans laquelle les données sont stockées sous forme de paires de valeurs clés. Dans lequel une clé unique correspond à 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 dans une for..ofboucle

Différences clés:

  • A Mapest ordonné et itérable, alors qu'un objet n'est pas ordonné et non itérable

  • Nous pouvons mettre n'importe quel type de données sous forme de Mapclé, tandis que les objets ne peuvent avoir qu'un nombre, une chaîne ou un symbole comme clé.

  • Un Maphérite de Map.prototype. Cela offre toutes sortes de fonctions et de propriétés utilitaires qui rendent le travail avec les Mapobjets beaucoup plus facile.

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

Willem van der Veen
la source
4

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

  • La spécification oblige les opérations cartographiques à être sublinéaires en moyenne.

    Toute implémentation non stupide d'objet 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 des comportements inattendus désagréables.

    Par exemple, supposons que vous n'ayez défini aucune foopropriété sur un objet nouvellement créé obj, vous vous attendez donc obj.fooà renvoyer undefined. Mais foopourrait être une propriété intégrée héritée de Object.prototype. Ou vous essayez de créer obj.fooen utilisant une affectation, mais un setter dans les Object.prototypeexécutions au lieu de stocker votre valeur.

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

Oriol
la source
4

Quand utiliser des cartes au lieu d'objets JavaScript simples?

L'objet JavaScript simple {key: 'value'} contient des données structurées. Mais un objet JS simple a ses limites:

  1. Seuls les chaînes et les symboles peuvent être utilisés comme clés d'objets. Si nous utilisons autre chose, les nombres comme clés d'un objet, alors pendant l'accès à ces clés, nous verrons que ces clés seront converties en chaînes, ce qui nous fera implicitement perdre la cohérence des types. const names = {1: 'un', 2: 'deux'}; Object.keys (noms); // ['1', '2']

  2. Il y a des chances d'écraser accidentellement les propriétés héritées des prototypes en écrivant des identificateurs JS en tant que noms clés d'un objet (par exemple toString, constructeur, etc.)

  3. Un autre objet ne peut pas être utilisé comme clé d'un objet, donc aucune information supplémentaire ne peut être écrite pour un objet en écrivant cet objet comme clé d'un autre objet et la valeur de cet autre objet contiendra les informations supplémentaires

  4. Les objets ne sont pas des itérateurs

  5. La taille d'un objet ne peut pas être déterminée directement

Ces limitations des objets sont résolues par les cartes, mais nous devons considérer les cartes comme un complément pour les objets au lieu de les remplacer. Fondamentalement, Map est simplement un tableau de tableaux, mais nous devons passer ce tableau de tableaux à l'objet Map en argument avec un nouveau mot-clé, sinon uniquement pour le tableau de tableaux, les propriétés et méthodes utiles de Map ne sont pas disponibles. Et rappelez-vous que les paires clé-valeur à l'intérieur du tableau de tableaux ou la carte doivent être séparées par des virgules uniquement, pas de deux-points comme dans les objets simples.

3 conseils pour décider d'utiliser une carte ou un objet:

  1. Utilisez des mappages sur des objets lorsque les clés sont inconnues jusqu'au moment de l'exécution, car les clés formées par l'entrée de l'utilisateur ou sans le savoir peuvent casser le code qui utilise l'objet si ces clés écrasent les propriétés héritées de l'objet, donc la carte est plus sûre dans ces cas. Utilisez également des cartes lorsque toutes les clés sont du même type et que toutes les cartes sont du même type.

  2. Utilisez des cartes s'il est nécessaire de stocker des valeurs primitives sous forme de clés.

  3. Utilisez des objets si nous devons opérer sur des éléments individuels.

Les avantages de l'utilisation de Maps sont les suivants:

1. La carte accepte tout type de clé et préserve le type de clé:

Nous savons que si la clé de l'objet n'est pas une chaîne ou un symbole, JS le transforme implicitement en chaîne. Au contraire, Map accepte tout type de clés: chaîne, nombre, booléen, symbole etc. et Map conserve le type de clé d'origine. Ici, nous utiliserons le numéro comme clé dans une carte et il restera un nombre:

const numbersMap= new Map();

numbersMap.set(1, 'one');

numbersMap.set(2, 'two');

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

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

À l'intérieur d'une carte, nous pouvons même utiliser un objet entier comme clé. Il peut y avoir des moments où nous voulons stocker certaines données liées à l'objet, sans attacher ces données à l'intérieur de l'objet lui-même afin que nous puissions travailler avec des objets allégés mais que nous voulons stocker des informations sur l'objet. Dans ces cas, nous devons utiliser Map afin de pouvoir faire Object comme clé et les données associées de l'objet comme valeur.

const foo= {name: foo};

const bar= {name: bar};

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

Mais l'inconvénient de cette approche est la complexité de l'accès à la valeur par clé, car nous devons parcourir l'ensemble du tableau pour obtenir la valeur souhaitée.

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

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

Nous pouvons résoudre ce problème de ne pas obtenir un accès direct à la valeur en utilisant une carte appropriée.

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'

Nous aurions pu le faire en utilisant WeakMap, il suffit d'écrire, const myMap = new WeakMap (). Les différences entre Map et WeakMap sont que WeakMap permet le ramasse-miettes des clés (ici les objets) afin d'éviter les fuites de mémoire, WeakMap n'accepte que les objets comme clés et WeakMap a réduit l'ensemble de méthodes.

2. La carte n'a aucune restriction sur les noms de clés:

Pour les objets JS simples, nous pouvons accidentellement remplacer les propriétés héritées du prototype et cela peut être dangereux. Ici, nous remplacerons la propriété toString () de l'objet acteur:

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

Définissons maintenant un fn isPlainObject () pour déterminer si l'argument fourni est un objet simple et ce fn utilise la méthode toString () pour le vérifier:

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

La carte n'a pas de restrictions sur les noms de clés, nous pouvons utiliser des noms de clés comme toString, constructeur, etc. Ici, bien que l'objet acteurMap ait une propriété nommée toString mais la méthode toString () héritée du prototype de l'objet acteurMap fonctionne parfaitement.

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 nous avons une situation où l'entrée utilisateur crée des clés, nous devons prendre ces clés à l'intérieur d'une carte au lieu d'un objet simple. En effet, l'utilisateur peut choisir un nom de champ personnalisé tel que toString, constructeur, etc., de tels noms de clé dans un objet brut peuvent potentiellement casser le code qui utilise ultérieurement cet objet. Donc, la bonne solution est de lier l'état de l'interface utilisateur à une carte, il n'y a aucun moyen de casser la carte:

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

3. La carte est itérable:

Pour itérer les propriétés d'un objet simple, nous avons besoin Object.entries () ou Object.keys (). Object.entries (plainObject) renvoie un tableau de paires de valeurs de clés extraites de l'objet, nous pouvons ensuite détruire ces clés et valeurs et obtenir une sortie normale de clés et de valeurs.

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

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

Comme les cartes sont itérables, c'est pourquoi nous n'avons pas besoin des méthodes des entrées () pour itérer sur une carte et la déstructuration de la clé, le tableau de valeurs peut être fait directement sur la carte car à l'intérieur d'une carte, chaque élément vit comme un tableau de paires de valeurs de clés séparées par des virgules .

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'

Map.keys () renvoie également un itérateur sur les clés et map.values ​​() renvoie un itérateur sur les valeurs.

4. Nous pouvons facilement connaître la taille d'une carte

Nous ne pouvons pas déterminer directement le nombre de propriétés dans un objet simple. Nous avons besoin d'un assistant fn comme Object.keys () qui retourne un tableau avec les clés de l'objet puis en utilisant la propriété length nous pouvons obtenir le nombre de clés ou la taille de l'objet ordinaire.

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

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

console.log(sizeOfObj);       // 2

Mais dans le cas de Maps, nous pouvons avoir un accès direct à la taille de la carte en utilisant la propriété map.size.

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

console.log(examsMap.size);
djb7861
la source
1

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

  • Utilisez des mappages sur des objets lorsque les clés sont inconnues jusqu'à l'exécution, et lorsque toutes les clés sont du même type et toutes les valeurs sont du même type.

  • Utilisez des mappages au cas où il serait nécessaire de stocker des valeurs primitives en tant que clés, car l'objet traite chaque clé comme une chaîne, que ce 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


la source
2
Ces conseils ne semblent pas particulièrement utiles, d'autant plus qu'il n'est pas facile de séparer les choses en fonction de ces critères. Je ne comprends pas avec le premier pourquoi les cartes sont un avantage lorsque les clés / valeurs sont du même type. Cela ressemble plus à essayer d'utiliser des objets comme des classes / structures, des cartes comme des collections. Le second est mal écrit et ne va pas droit au but. Cela signifie vraiment utiliser des cartes lorsque vous avez des types équivalents de chaînes mixtes ("1" et 1) ou lorsque vous avez besoin / souhaitez conserver les types de clés. La dernière je pense que c'est la même chose que la première, c'est en supposant que vous ne savez pas ce qu'est un objet donc c'est vague.
jgmjgm
1

C'est un court moyen pour moi de m'en souvenir: KOI

  1. Clés. La clé d'objet est des chaînes ou des symboles. Les clés de carte peuvent également être des nombres (1 et "1" sont différents), des objets NaN, etc. Elle permet ===de distinguer les clés, à une exception près, NaN !== NaNmais vous pouvez utiliserNaN comme clé.
  2. Ordre. L'ordre d'insertion est mémorisé. Alors [...map]ou[...map.keys()] a un ordre particulier.
  3. Interface. Objet: obj[key]ou obj.a(dans certains langages, []et []=font vraiment partie de l'interface). Plan a get(), set(), has(), delete()etc. Notez que vous pouvez utiliser , map[123]mais qui l'utilise comme un objet JS plaine.
non-polarité
la source
0

Selon Mozilla

Objet vs carte en JavaScript en bref avec des exemples.

Objet: suit le même concept que celui de la carte, c'est-à-dire en utilisant une paire clé-valeur pour stocker des données. Mais il existe de légères différences qui rendent la carte plus performante dans certaines situations.

Map- est une structure de données qui aide à stocker les données sous forme de paires. La paire se compose d'une clé unique et d'une valeur mappée à la clé. Il aide à prévenir la duplicité.

Différences clés

  • La carte est une instance d'un objet mais l'inverse n'est pas vrai.

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

  • Dans Object, le type de données du champ-clé est limité aux entiers, aux chaînes et aux symboles. Alors que dans Map, le champ clé peut être de n'importe quel type de données (entier, un tableau, un objet)

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)

  • Dans la carte, l'ordre d'origine des éléments est conservé. Ce n'est pas vrai dans le cas d'objets.

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

Vahid Akhtar
la source