Der Hauptunterschied besteht darin, dass Objekte nur Zeichenfolgenschlüssel unterstützen, während Maps mehr oder weniger jeden Schlüsseltyp unterstützen.
Wenn ich das tue obj[123] = true
und dann Object.keys(obj)
werde ich ["123"]
eher als bekommen [123]
. Eine Karte würde den Typ des Schlüssels beibehalten und zurückkehren, [123]
was großartig ist. Mit Karten können Sie auch Objekte als Schlüssel verwenden. Um dies zu tun, müsste man Objekten traditionell eine eindeutige Kennung geben, um sie zu hashen (ich glaube nicht, dass ich jemals so etwas wie getObjectId
in JS als Teil des Standards gesehen habe). Karten garantieren auch die Aufrechterhaltung der Ordnung, sind also rundum besser für die Erhaltung und können Ihnen manchmal ersparen, dass Sie einige Sortierungen vornehmen müssen.
Zwischen Karten und Objekten gibt es in der Praxis mehrere Vor- und Nachteile. Objekte haben sowohl Vor- als auch Nachteile, da sie sehr eng in den Kern von JavaScript integriert sind, wodurch sie sich von Map deutlich über den Unterschied in der Schlüsselunterstützung hinaus unterscheiden.
Ein unmittelbarer Vorteil besteht darin, dass Sie Objekte syntaktisch unterstützen, um den Zugriff auf Elemente zu vereinfachen. Sie haben auch direkte Unterstützung für JSON. Wenn es als Hash verwendet wird, ist es ärgerlich, ein Objekt ohne Eigenschaften zu erhalten. Wenn Sie Objekte als Hash-Tabelle verwenden möchten, werden sie standardmäßig verschmutzt, und Sie müssen sie häufig aufrufen hasOwnProperty
, wenn Sie auf Eigenschaften zugreifen. Hier können Sie sehen, wie Objekte standardmäßig verschmutzt werden und wie hoffentlich nicht verschmutzte Objekte zur Verwendung als Hashes erstellt werden:
({}).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
Verschmutzung von Objekten macht Code nicht nur ärgerlicher, langsamer usw., sondern kann auch potenzielle Konsequenzen für die Sicherheit haben.
Objekte sind keine reinen Hash-Tabellen, sondern versuchen, mehr zu tun. Sie haben Kopfschmerzen wie hasOwnProperty
, nicht in der Lage zu sein, die Länge leicht zu bekommen ( Object.keys(obj).length
) und so weiter. Objekte sind nicht nur als Hash-Maps gedacht, sondern auch als dynamisch erweiterbare Objekte. Wenn Sie sie also als reine Hash-Tabellen verwenden, treten Probleme auf.
Vergleich / Liste verschiedener gängiger Operationen:
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();
Es gibt einige andere Optionen, Ansätze, Methoden usw. mit unterschiedlichen Höhen und Tiefen (Leistung, knapp, tragbar, erweiterbar usw.). Objekte sind etwas seltsam, da sie den Kern der Sprache bilden. Sie haben also viele statische Methoden, um mit ihnen zu arbeiten.
Neben dem Vorteil, dass Karten Schlüsseltypen beibehalten und Objekte wie Schlüssel unterstützen können, sind sie von den Nebenwirkungen isoliert, die Objekte häufig haben. Eine Karte ist ein reiner Hash. Es gibt keine Verwirrung darüber, gleichzeitig ein Objekt zu sein. Karten können auch einfach mit Proxy-Funktionen erweitert werden. Objekte haben derzeit eine Proxy-Klasse, aber die Leistung und die Speichernutzung sind schlecht. Tatsächlich erstellt das Erstellen eines eigenen Proxys, der so aussieht, als ob Map for Objects derzeit eine bessere Leistung als Proxy aufweist.
Ein wesentlicher Nachteil von Maps besteht darin, dass sie mit JSON nicht direkt unterstützt werden. Das Parsen ist möglich, hat jedoch mehrere Probleme:
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;
});
Das oben Gesagte führt zu einem ernsthaften Leistungseinbruch und unterstützt auch keine String-Tasten. Die JSON-Codierung ist noch schwieriger und problematischer (dies ist einer von vielen Ansätzen):
// 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())
});
};
Dies ist nicht so schlimm, wenn Sie nur Maps verwenden, aber Probleme haben, wenn Sie Typen mischen oder nicht skalare Werte als Schlüssel verwenden (nicht, dass JSON für diese Art von Problem perfekt ist, wie es ist, IE-Zirkelobjektreferenz). Ich habe es nicht getestet, aber es besteht die Möglichkeit, dass es die Leistung im Vergleich zu Stringify stark beeinträchtigt.
Andere Skriptsprachen haben häufig keine derartigen Probleme, da sie explizite nicht skalare Typen für Map, Object und Array haben. Webentwicklung ist oft ein Problem bei nicht skalaren Typen, bei denen Sie sich mit Dingen wie PHP-Zusammenführung von Array / Map mit Objekt unter Verwendung von A / M für Eigenschaften und JS-Zusammenführung von Map / Objekt mit Array-Erweiterung von M / O befassen müssen. Das Zusammenführen komplexer Typen ist der Fluch des Teufels für hochrangige Skriptsprachen.
Bisher sind dies hauptsächlich Probleme bei der Implementierung, aber auch die Leistung für grundlegende Operationen ist wichtig. Die Leistung ist auch komplex, da sie vom Motor und der Verwendung abhängt. Machen Sie meine Tests mit einem Körnchen Salz, da ich keinen Fehler ausschließen kann (ich muss mich beeilen). Sie sollten auch Ihre eigenen Tests durchführen, um zu bestätigen, dass meine nur sehr spezifische einfache Szenarien untersuchen, um nur einen groben Hinweis zu geben. Laut Tests in Chrome für sehr große Objekte / Karten ist die Leistung für Objekte aufgrund des Löschens schlechter, was anscheinend eher proportional zur Anzahl der Schlüssel als zu O (1) ist:
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 hat eindeutig einen starken Vorteil beim Abrufen und Aktualisieren, aber die Löschleistung ist schrecklich. Karten verbrauchen in diesem Fall eine winzige Menge mehr Speicher (Overhead), aber da nur ein Objekt / eine Karte mit Millionen von Schlüsseln getestet wird, wird die Auswirkung des Overheads für Karten nicht gut ausgedrückt. Mit der Speicherverwaltung scheinen Objekte auch früher frei zu werden, wenn ich das Profil richtig lese, was ein Vorteil zugunsten von Objekten sein könnte.
In Firefox ist dies für diesen speziellen Benchmark eine andere Geschichte:
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
Ich möchte sofort darauf hinweisen, dass das Löschen von Objekten in Firefox in diesem speziellen Benchmark keine Probleme verursacht. In anderen Benchmarks hat es jedoch Probleme verursacht, insbesondere wenn es wie in Chrome viele Schlüssel gibt. Karten sind in Firefox für große Sammlungen eindeutig überlegen.
Dies ist jedoch nicht das Ende der Geschichte. Was ist mit vielen kleinen Objekten oder Karten? Ich habe einen schnellen Benchmark durchgeführt, aber keinen erschöpfenden (Einstellen / Erhalten), der mit einer kleinen Anzahl von Schlüsseln in den obigen Operationen am besten funktioniert. Bei diesem Test geht es mehr um Speicher und Initialisierung.
Map Create: 69 // new Map
Object Create: 34 // {}
Auch diese Zahlen variieren, aber im Grunde hat Object einen guten Vorsprung. In einigen Fällen ist der Vorsprung für Objekte gegenüber Karten extrem (~ 10-mal besser), aber im Durchschnitt war er etwa 2-3-mal besser. Es scheint, dass extreme Leistungsspitzen in beide Richtungen funktionieren können. Ich habe dies nur in Chrome und bei der Erstellung getestet, um die Speichernutzung und den Overhead zu profilieren. Ich war ziemlich überrascht zu sehen, dass in Chrome Karten mit einem Schlüssel etwa 30-mal mehr Speicher belegen als Objekte mit einem Schlüssel.
Zum Testen vieler kleiner Objekte mit allen oben genannten Vorgängen (4 Tasten):
Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139
In Bezug auf die Speicherzuordnung verhielten sich diese in Bezug auf Freigabe / GC gleich, aber Map verwendete fünfmal mehr Speicher. Bei diesem Test wurden 4 Schlüssel verwendet, wobei ich wie im letzten Test nur einen Schlüssel festgelegt habe, um die Reduzierung des Speicheraufwands zu erklären. Ich habe diesen Test einige Male durchgeführt und Map / Object sind für Chrome in Bezug auf die Gesamtgeschwindigkeit insgesamt mehr oder weniger gleichauf. In Firefox für kleine Objekte gibt es einen deutlichen Leistungsvorteil gegenüber Karten insgesamt.
Dies beinhaltet natürlich nicht die einzelnen Optionen, die stark variieren können. Ich würde keine Mikrooptimierung mit diesen Zahlen empfehlen. Was Sie daraus ziehen können, ist, dass Sie als Faustregel Karten für sehr große Schlüsselwertspeicher und Objekte für kleine Schlüsselwertspeicher stärker berücksichtigen sollten.
Darüber hinaus ist es die beste Strategie mit diesen beiden, es zu implementieren und es zuerst funktionieren zu lassen. Bei der Profilerstellung ist zu beachten, dass manchmal Dinge, von denen Sie nicht glauben, dass sie beim Betrachten langsam sind, aufgrund von Engine-Macken, wie sie beim Löschen von Objektschlüsseln auftreten, unglaublich langsam sein können.