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] = true
et 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 getObjectId
JS 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 hasOwnProperty
lors 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 hasOwnProperty
ne 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.