Karte gegen Objekt in JavaScript


290

Ich habe gerade chromestatus.com entdeckt und nach einigen Stunden meines Tages diesen Funktionseintrag gefunden :

Karte: Kartenobjekte sind einfache Schlüssel- / Wertekarten.

Das hat mich verwirrt. Normale JavaScript-Objekte sind Wörterbücher. Wie Mapunterscheidet sich ein Wörterbuch von einem Wörterbuch? Konzeptionell sind sie identisch (gemäß Was ist der Unterschied zwischen einer Karte und einem Wörterbuch? )

Die Dokumentation chromestatus Referenzen hilft auch nicht:

Kartenobjekte sind Sammlungen von Schlüssel / Wert-Paaren, bei denen sowohl die Schlüssel als auch die Werte beliebige ECMAScript-Sprachwerte sein können. Ein eindeutiger Schlüsselwert darf nur in einem Schlüssel / Wert-Paar in der Map-Sammlung vorkommen. Unterscheiden Sie Schlüsselwerte, die mithilfe eines Vergleichsalgorithmus unterschieden werden, der beim Erstellen der Karte ausgewählt wird.

Ein Map-Objekt kann seine Elemente in Einfügereihenfolge durchlaufen. Das Kartenobjekt muss entweder mithilfe von Hash-Tabellen oder anderen Mechanismen implementiert werden, die im Durchschnitt Zugriffszeiten bereitstellen, die sublinear zur Anzahl der Elemente in der Sammlung sind. Die in dieser Map-Objektspezifikation verwendeten Datenstrukturen sollen nur die erforderliche beobachtbare Semantik von Map-Objekten beschreiben. Es soll kein tragfähiges Implementierungsmodell sein.

… Klingt für mich immer noch wie ein Objekt, also habe ich eindeutig etwas verpasst.

Warum erhält JavaScript ein (gut unterstütztes) MapObjekt? Was tut es?


Antworten:


286

Laut Mozilla:

Ein Map-Objekt kann seine Elemente in Einfügereihenfolge iterieren. Eine for..of-Schleife gibt für jede Iteration ein Array von [Schlüssel, Wert] zurück.

und

Objekte ähneln Maps dahingehend, dass Sie beide Schlüssel auf Werte setzen, diese Werte abrufen, Schlüssel löschen und erkennen können, ob etwas auf einem Schlüssel gespeichert ist. Aus diesem Grund wurden Objekte in der Vergangenheit als Karten verwendet. Es gibt jedoch wichtige Unterschiede zwischen Objekten und Karten, die die Verwendung einer Karte verbessern.

Ein Objekt hat einen Prototyp, daher gibt es Standardschlüssel in der Karte. Dies kann jedoch mit map = Object.create (null) umgangen werden. Die Schlüssel eines Objekts sind Zeichenfolgen, wobei sie einen beliebigen Wert für eine Karte haben können. Sie können die Größe einer Karte leicht ermitteln, während Sie die Größe eines Objekts manuell verfolgen müssen.

Verwenden Sie Karten über Objekten, wenn Schlüssel bis zur Laufzeit unbekannt sind und wenn alle Schlüssel vom gleichen Typ und alle Werte vom gleichen Typ sind.

Verwenden Sie Objekte, wenn es eine Logik gibt, die einzelne Elemente bearbeitet.

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

Die Iterierbarkeit in der richtigen Reihenfolge ist eine Funktion, die von Entwicklern seit langem gewünscht wird, auch weil sie in allen Browsern die gleiche Leistung gewährleistet. Für mich ist das eine große Sache.

Die myMap.has(key)Methode wird besonders praktisch sein, und auch die myMap.sizeEigenschaft.


13
Ein Nachteil ist vermutlich, dass eine Karte mehr Speicher benötigt (jedoch innerhalb derselben Größenordnung), um die Einfügereihenfolge beizubehalten.
John Kurlak

4
Karten haben neben der hier erwähnten Reihenfolge noch andere Funktionen (Verwendung eines beliebigen Objekts als Schlüssel, Trennung von Schlüsseln und Requisiten usw.), aber in einigen Fällen wird die Iterationsreihenfolge der einfachen Objekteigenschaften durch ES2015 definiert. Siehe stackoverflow.com/a/32149345 .
JMM

2
Ich habe die Bedeutung nicht verstanden, wenn Sie sagen, ein Objekt hat einen Prototyp, daher gibt es Standardschlüssel in der Karte. Dies kann jedoch mit umgangen werdenmap = Object.create(null) . Was sind Standardschlüssel? Wie hängen Schlüssel zusammen Object.prototype?
Überaustausch

4
Meine Tests in Chrome haben gezeigt, dass Karten keine nennenswerte Menge mehr Speicher für die Aufrechterhaltung der Reihenfolge benötigen. Ich glaube, es gab 0,1 KB mehr für eine Million Schlüssel und ich glaube nicht, dass dies der Aufrechterhaltung der Ordnung diente. Diese ~ 0,1 KB scheinen jedoch ein konstanter Overhead zu sein. Wenn Sie stattdessen eine Million Karten mit einem Schlüssel erstellen und vergleichen, ist dieser viel größer als das Objekt.
jgmjgm

2
@luxon Sie erstellen dort ein Objekt. Nach der ES6-Spezifikation muss der newOperator mit dem MapSymbol verwendet werden, dh new Mapum ein Kartenobjekt zu erstellen. var a = {}ist eine Abkürzung für (bedeutet äquivalent zu)var a = Object.create(Object.prototype)
dudewad

104

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] = trueund 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 getObjectIdin 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.


Die mangelnde Serialisierbarkeit war für viele Entwickler ein echtes Problem. Schauen Sie sich die positive Bewertung von Wie kann ich eine ES6-Karte in localstorage (oder anderswo) beibehalten ? und Wie können Sie eine ES6-Karte mit JSON.stringify erstellen? .
Franklin Yu

Ist die Zahl in Millisekunden, Bytes oder Gesamtobjekten?
StefansArya

Hat so ms gedauert (etwas hat kurz gebraucht, um etwas Verwendetes zu sagen, daher verbraucht es in diesem Fall Zeit). Obwohl dies ein alter Test ist und ich den Benchmark-Code nicht mehr habe. Es ist jetzt wahrscheinlich ganz anders. Ich glaube, das Löschproblem ist behoben.
jgmjgm

27

Ich glaube nicht, dass die folgenden Punkte in den Antworten bisher erwähnt wurden, und ich dachte, dass sie erwähnenswert wären.


Karten können größer sein

In Chrom kann ich 16,7 Mio. Schlüssel / Wert - Paare mit Mapvs. 11.1 Mio. mit einem regelmäßigen Objekt. Fast genau 50% mehr Paare mit a Map. Beide belegen ungefähr 2 GB Speicher, bevor sie abstürzen, und ich denke, dies hängt möglicherweise mit der Speicherbeschränkung durch Chrom zusammen ( Bearbeiten : Ja, versuchen Sie, 2 zu füllen, Mapsund Sie erhalten jeweils nur 8,3 Millionen Paare, bevor es abstürzt). Sie können es selbst mit diesem Code testen (führen Sie sie separat und natürlich nicht gleichzeitig aus):

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

Objekte haben bereits einige Eigenschaften / Schlüssel

Dieser hat mich schon mal gestolpert. Regelmäßige Objekte haben toString, constructor, valueOf, hasOwnProperty, isPrototypeOfund eine Reihe von anderen bereits vorhandenen Eigenschaften. Dies mag für die meisten Anwendungsfälle kein großes Problem sein, hat mir jedoch zuvor Probleme bereitet.

Karten können langsamer sein:

Aufgrund des .getFunktionsaufrufaufwands und des Mangels an interner Optimierung kann Map für einige Aufgaben erheblich langsamer sein als ein einfaches altes JavaScript-Objekt.


1
Wiegt Ihrer Meinung nach die Semantik hier die Leistung auf? Karten klingen perfekt, wenn Sie ein Wörterbuch benötigen, aber es ist schwierig, eine langsamere Suche zu akzeptieren. Ist eine schnelle Suche nicht der ganze Sinn von Wörterbüchern?
user2954463

3
Ich würde auf jeden Fall mit einfachen alten Objekte gehen , wenn Sie gut mit 11 Millionen Schlüssel / Wert - Paare und nicht über die bereits vorhandenen Tasten wie Pflege toString, constructoretc. (dh Ihre Schlüssel sind extrem unwahrscheinlich , kollidiert mit ihnen). Es ist einfacher, mit ihnen zu arbeiten - z. B. ist das Inkrementieren so obj[i] = (obj[i] || 0) + 1, während Mapes immer map.set(i, (map.get(i) || 0) + 1)noch nicht so schlimm ist, aber es zeigt nur, wie die Dinge unnötig chaotisch werden können. Karten haben definitiv ihre Anwendungsfälle, aber oft reicht ein einfaches Objekt aus.

Beachten Sie, dass Sie den Standard loswerden können toString, constructor(etc.) Objekteigenschaften durch das Schreiben obj = Object.create(null)statt obj = {}.

17

Objekte können sich wie Wörterbücher verhalten, da Javascript dynamisch eingegeben wird, sodass Sie jederzeit Eigenschaften zu einem Objekt hinzufügen oder daraus entfernen können.

Die neue Map()Funktionalität ist jedoch viel besser, weil:

  • Es bietet get, set, has, und deleteMethoden.
  • Akzeptiert einen beliebigen Typ für die Schlüssel anstelle von nur Zeichenfolgen.
  • Bietet einen Iterator für die einfache for-ofVerwendung und behält die Reihenfolge der Ergebnisse bei.
  • Es gibt keine Randfälle mit Prototypen und anderen Eigenschaften, die während der Iteration oder des Kopierens angezeigt werden.
  • Es unterstützt Millionen von Elementen.
  • Ist sehr schnell und wird immer schneller, wenn die Javascript-Engines besser werden.

In 99% der Fälle sollten Sie nur a verwenden Map(). Wenn Sie jedoch nur Zeichenfolgen-basierte Schlüssel verwenden und maximale Leseleistung benötigen, sind Objekte möglicherweise die bessere Wahl.

Das Detail ist, dass (fast alle) Javascript-Engines Objekte im Hintergrund bis zu C ++ - Klassen kompilieren. Diese Typen werden zwischengespeichert und anhand ihrer "Gliederung" wiederverwendet. Wenn Sie also ein neues Objekt mit denselben genauen Eigenschaften erstellen, verwendet die Engine eine vorhandene Hintergrundklasse erneut. Der Zugriffspfad für Eigenschaften dieser Klassen ist sehr optimiert und viel schneller als die Suche nach a Map().

Durch Hinzufügen oder Entfernen einer Eigenschaft wird die zwischengespeicherte Hintergrundklasse neu kompiliert, weshalb die Verwendung eines Objekts als Wörterbuch mit vielen Schlüsseladditionen und -löschungen sehr langsam ist, das Lesen und Zuweisen vorhandener Schlüssel ohne Änderung des Objekts jedoch sehr schnell erfolgt.

Wenn Sie also eine einmal lesbare Arbeitslast mit Zeichenfolgenschlüsseln haben, verwenden Sie ein objectals spezialisiertes Hochleistungswörterbuch, aber für alles andere verwenden Sie ein Map().


Objekt bietet auch get set has deleteetc. Funktionalität, es ist einfach nicht ganz so elegant (aber auch nicht schlecht). Inwiefern ist es Mapeinfacher, Iterationen durchzuführen? Ich bin mir nicht sicher, ob ich zustimmen kann.
Andrew

@ Andrew Ich beschäftige mich mit den Methoden, und die Funktionalität ist auch unterschiedlich, je nachdem, was Sie verwenden und das Ergebnis. Das Iterieren ist einfacher, da Prototyp- und native Eigenschaften nicht in der Schleife angezeigt werden und ein normaler JS-Iterator verwendet wird, der dieselbe Reihenfolge beibehält.
Mani Gandham

11

Zusätzlich zu den anderen Antworten habe ich festgestellt, dass Karten unhandlicher und ausführlicher zu handhaben sind als Objekte.

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

Dies ist wichtig, da kürzerer Code schneller zu lesen, direkter auszudrücken und besser im Kopf des Programmierers bleibt .

Ein weiterer Aspekt: ​​Da set () die Karte und nicht den Wert zurückgibt, ist es unmöglich, Zuordnungen zu verketten.

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

Das Debuggen von Karten ist auch schmerzhafter. Unten können Sie nicht sehen, welche Schlüssel sich auf der Karte befinden. Sie müssten Code schreiben, um das zu tun.

Viel Glück bei der Bewertung eines Map Iterator

Objekte können von jeder IDE ausgewertet werden:

WebStorm bewertet ein Objekt


4
Angesichts all dessen scheint die Karte eine vorzeitige Optimierung zu sein.
PRMan

10

Zusammenfassung:

  • Object: Eine Datenstruktur, in der Daten als Schlüsselwertpaare gespeichert werden. In einem Objekt muss der Schlüssel eine Zahl, eine Zeichenfolge oder ein Symbol sein. Der Wert kann alles sein, also auch andere Objekte, Funktionen usw. Ein Objekt ist eine nicht geordnete Datenstruktur, dh die Reihenfolge des Einfügens von Schlüsselwertpaaren wird nicht gespeichert
  • ES6 Map: Eine Datenstruktur, in der Daten als Schlüsselwertpaare gespeichert werden. In dem ein eindeutiger Schlüssel einem Wert zugeordnet ist . Sowohl der Schlüssel als auch der Wert können in einem beliebigen Datentyp vorliegen . Eine Karte ist eine iterierbare Datenstruktur. Dies bedeutet, dass die Reihenfolge der Einfügung gespeichert wird und wir auf die Elemente in z. B. einer for..ofSchleife zugreifen können

Hauptunterschiede:

  • A Mapist geordnet und iterierbar, während ein Objekt nicht geordnet und nicht iterierbar ist

  • Wir können jeden Datentyp als MapSchlüssel verwenden, während Objekte nur eine Zahl, eine Zeichenfolge oder ein Symbol als Schlüssel haben können.

  • A Maperbt von Map.prototype. Dies bietet alle Arten von Dienstprogrammfunktionen und -eigenschaften, was die Arbeit mit MapObjekten erheblich erleichtert.

Beispiel:

Objekt:

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)

Karte:

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

Quelle: MDN


4

-0Maps können nicht nur in einer genau definierten Reihenfolge iteriert werden und beliebige Werte als Schlüssel verwenden (außer ), sondern auch aus folgenden Gründen nützlich sein:

  • Die Spezifikation erzwingt, dass Kartenoperationen im Durchschnitt sublinear sind.

    Jede nicht dumme Implementierung eines Objekts verwendet eine Hash-Tabelle oder ähnliches, sodass die Suche nach Eigenschaften im Durchschnitt wahrscheinlich konstant ist. Dann könnten Objekte sogar schneller sein als Karten. Dies ist jedoch in der Spezifikation nicht vorgeschrieben.

  • Objekte können unangenehme unerwartete Verhaltensweisen aufweisen.

    Angenommen, Sie haben keine fooEigenschaft für ein neu erstelltes Objekt festgelegt obj, sodass Sie eine obj.fooundefinierte Rückgabe erwarten . Könnte fooaber eingebautes Eigentum von geerbt werden Object.prototype. Oder Sie versuchen, obj.foomithilfe einer Zuweisung zu erstellen , aber ein Setter wird ausgeführt, Object.prototypeanstatt Ihren Wert zu speichern.

    Karten verhindern solche Dinge. Nun, es sei denn, ein Skript bringt etwas durcheinander Map.prototype. Und Object.create(null)würde auch funktionieren, aber dann verlieren Sie die einfache Objektinitialisierersyntax.


4

Wann sollten Maps anstelle von einfachen JavaScript-Objekten verwendet werden?

Das einfache JavaScript-Objekt {key: 'value'} enthält strukturierte Daten. Ein einfaches JS-Objekt hat jedoch seine Grenzen:

  1. Nur Zeichenfolgen und Symbole können als Schlüssel für Objekte verwendet werden. Wenn wir andere Dinge verwenden, z. B. Zahlen als Schlüssel eines Objekts, werden wir beim Zugriff auf diese Schlüssel sehen, dass diese Schlüssel implizit in Zeichenfolgen konvertiert werden, was dazu führt, dass wir die Konsistenz der Typen verlieren. const names = {1: 'one', 2: 'two'}; Object.keys (Namen); // ['1', '2']

  2. Es besteht die Möglichkeit, dass ererbte Eigenschaften von Prototypen versehentlich überschrieben werden, indem JS-Bezeichner als Schlüsselnamen eines Objekts geschrieben werden (z. B. toString, Konstruktor usw.).

  3. Ein anderes Objekt kann nicht als Schlüssel eines Objekts verwendet werden, daher können keine zusätzlichen Informationen für ein Objekt geschrieben werden, indem dieses Objekt als Schlüssel eines anderen Objekts geschrieben wird und der Wert dieses anderen Objekts die zusätzlichen Informationen enthält

  4. Objekte sind keine Iteratoren

  5. Die Größe eines Objekts kann nicht direkt bestimmt werden

Diese Einschränkungen von Objekten werden durch Karten gelöst, aber wir müssen Karten als Ergänzung für Objekte betrachten, anstatt sie zu ersetzen. Grundsätzlich ist Map nur ein Array von Arrays, aber wir müssen dieses Array von Arrays als Argument mit dem neuen Schlüsselwort an das Map-Objekt übergeben, da sonst nur für Arrays von Arrays die nützlichen Eigenschaften und Methoden von Map nicht verfügbar sind. Und denken Sie daran, dass Schlüssel-Wert-Paare innerhalb des Arrays von Arrays oder der Map nur durch Kommas getrennt werden dürfen, keine Doppelpunkte wie bei einfachen Objekten.

3 Tipps zur Entscheidung, ob Sie eine Karte oder ein Objekt verwenden möchten:

  1. Verwenden Sie Zuordnungen über Objekte, wenn Schlüssel bis zur Laufzeit unbekannt sind, da Schlüssel, die durch Benutzereingaben oder unwissentlich gebildet wurden, den Code, der das Objekt verwendet, beschädigen können, wenn diese Schlüssel die geerbten Eigenschaften des Objekts überschreiben. Daher ist die Zuordnung in diesen Fällen sicherer. Verwenden Sie auch Karten, wenn alle Schlüssel vom gleichen Typ und alle Karten vom gleichen Typ sind.

  2. Verwenden Sie Karten, wenn primitive Werte als Schlüssel gespeichert werden müssen.

  3. Verwenden Sie Objekte, wenn Sie einzelne Elemente bearbeiten müssen.

Die Verwendung von Karten bietet folgende Vorteile:

1. Map akzeptiert jeden Schlüsseltyp und behält den Schlüsseltyp bei:

Wir wissen, dass JS den Schlüssel des Objekts implizit in eine Zeichenfolge umwandelt, wenn es sich nicht um eine Zeichenfolge oder ein Symbol handelt. Im Gegenteil, Map akzeptiert alle Arten von Schlüsseln: Zeichenfolge, Zahl, Boolescher Wert, Symbol usw. und Map behält den ursprünglichen Schlüsseltyp bei. Hier verwenden wir die Nummer als Schlüssel in einer Karte und es bleibt eine Nummer:

const numbersMap= new Map();

numbersMap.set(1, 'one');

numbersMap.set(2, 'two');

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

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

Innerhalb einer Karte können wir sogar ein ganzes Objekt als Schlüssel verwenden. Es kann vorkommen, dass wir einige objektbezogene Daten speichern möchten, ohne diese Daten im Objekt selbst anzuhängen, damit wir mit schlanken Objekten arbeiten können, aber einige Informationen über das Objekt speichern möchten. In diesen Fällen müssen wir Map verwenden, damit wir Object als Schlüssel und zugehörige Daten des Objekts als Wert festlegen können.

const foo= {name: foo};

const bar= {name: bar};

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

Der Nachteil dieses Ansatzes ist jedoch die Komplexität des Zugriffs auf den Wert per Schlüssel, da wir das gesamte Array durchlaufen müssen, um den gewünschten Wert zu erhalten.

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

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

Wir können dieses Problem lösen, indem wir keinen direkten Zugriff auf den Wert erhalten, indem wir eine geeignete Karte verwenden.

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'

Wir hätten dies mit WeakMap tun können, müssen nur schreiben, const myMap = new WeakMap (). Die Unterschiede zwischen Map und WeakMap bestehen darin, dass WeakMap die Speicherbereinigung von Schlüsseln (hier Objekte) ermöglicht, um Speicherverluste zu vermeiden. WeakMap akzeptiert nur Objekte als Schlüssel und WeakMap hat die Anzahl der Methoden reduziert.

2. Map hat keine Einschränkung hinsichtlich der Schlüsselnamen:

Bei einfachen JS-Objekten können wir versehentlich vom Prototyp geerbte Eigenschaften überschreiben, was gefährlich sein kann. Hier überschreiben wir die toString () - Eigenschaft des Actor-Objekts:

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

Definieren wir nun ein fn isPlainObject (), um festzustellen, ob das angegebene Argument ein einfaches Objekt ist, und dieses fn verwendet die toString () -Methode, um es zu überprüfen:

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

Die Map unterliegt keinen Einschränkungen für die Schlüsselnamen. Wir können Schlüsselnamen wie toString, Konstruktor usw. verwenden. Hier hat das ActorMap-Objekt zwar eine Eigenschaft namens toString, aber die vom Prototyp des ActorMap-Objekts geerbte Methode toString () funktioniert einwandfrei.

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

Wenn wir eine Situation haben, in der Benutzereingaben Schlüssel erstellen, müssen wir diese Schlüssel in einer Karte anstelle eines einfachen Objekts verwenden. Dies liegt daran, dass der Benutzer möglicherweise einen benutzerdefinierten Feldnamen wie toString, Konstruktor usw. auswählt. Diese Schlüsselnamen in einem einfachen Objekt können möglicherweise den Code beschädigen, der dieses Objekt später verwendet. Die richtige Lösung besteht also darin, den Status der Benutzeroberfläche an eine Karte zu binden. Es gibt keine Möglichkeit, die Karte zu beschädigen:

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

3. Karte ist iterierbar:

Um die Eigenschaften eines einfachen Objekts zu iterieren, benötigen wir Object.entries () oder Object.keys (). Die Object.entries (plainObject) geben ein Array von Schlüsselwertpaaren zurück, die aus dem Objekt extrahiert wurden. Anschließend können wir diese Schlüssel und Werte zerstören und normale Schlüssel und Werte ausgeben.

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

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

Da Maps iterierbar sind, benötigen wir keine entry () -Methoden, um über eine Map zu iterieren und den Schlüssel zu zerstören. Das Wertearray kann direkt auf der Map ausgeführt werden, da in einer Map jedes Element als Array von Schlüsselwertpaaren lebt, die durch Kommas getrennt sind .

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'

Außerdem gibt map.keys () einen Iterator über Schlüssel und map.values ​​() einen Iterator über Werte zurück.

4. Wir können die Größe einer Karte leicht erkennen

Wir können die Anzahl der Eigenschaften in einem einfachen Objekt nicht direkt bestimmen. Wir benötigen einen Helfer fn wie Object.keys (), der ein Array mit Schlüsseln des Objekts zurückgibt. Mit der Eigenschaft length können wir dann die Anzahl der Schlüssel oder die Größe des einfachen Objekts ermitteln.

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

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

console.log(sizeOfObj);       // 2

Bei Karten können wir jedoch mit der Eigenschaft map.size direkt auf die Größe der Karte zugreifen.

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

console.log(examsMap.size);

1

Diese beiden Tipps können Ihnen bei der Entscheidung helfen, ob Sie eine Karte oder ein Objekt verwenden möchten:

  • Verwenden Sie Karten über Objekten, wenn Schlüssel bis zur Laufzeit unbekannt sind und wenn alle Schlüssel vom gleichen Typ und alle Werte vom gleichen Typ sind.

  • Verwenden Sie Maps für den Fall, dass primitive Werte als Schlüssel gespeichert werden müssen, da das Objekt jeden Schlüssel als Zeichenfolge behandelt, entweder als Zahlenwert, als booleschen Wert oder als einen anderen primitiven Wert.

  • Verwenden Sie Objekte, wenn es eine Logik gibt, die einzelne Elemente bearbeitet.

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


2
Diese Tipps sehen nicht besonders hilfreich aus, zumal es nicht einfach ist, Dinge nach diesen Kriterien aufzuteilen. Ich verstehe nicht mit dem ersten, warum Karten ein Vorteil sind, wenn Schlüssel / Werte vom gleichen Typ sind. Es klingt eher so, als würde man versuchen, Objekte wie Klassen / Strukturen, Karten wie Sammlungen zu verwenden. Der zweite ist schlecht geschrieben und kommt nicht auf den Punkt. Es bedeutet wirklich, Karten zu verwenden, wenn Sie gemischte Zeichenfolgenäquivalenttypen ("1" und 1) haben oder wenn Sie Schlüsseltypen beibehalten möchten / möchten. Das letzte Mal denke ich, dass es das gleiche ist wie das erste, vorausgesetzt, Sie wissen nicht, was ein Objekt ist, also ist es vage.
jgmjgm

0

Dies ist eine kurze Möglichkeit für mich, mich daran zu erinnern: KOI

  1. Schlüssel. Objektschlüssel sind Zeichenfolgen oder Symbole. Zuordnungsschlüssel können auch Zahlen (1 und "1" sind unterschiedlich), Objekte NaNusw. sein. Sie werden verwendet ===, um zwischen Schlüsseln zu unterscheiden, mit einer Ausnahme NaN !== NaN, die Sie jedoch NaNals Schlüssel verwenden können.
  2. Auftrag. Die Einfügereihenfolge wird gespeichert. Also [...map]oder [...map.keys()]hat eine bestimmte Reihenfolge.
  3. Schnittstelle. Objekt: obj[key]oder obj.a(in einer Sprache, []und []=ist wirklich Teil der Schnittstelle). Karte hat get(), set(), has(), delete()usw. Beachten Sie, dass Sie verwenden können , map[123]sondern dass es als ein einfaches JS - Objekt verwendet.

0

Laut Mozilla

Objekt gegen Karte in JavaScript kurz mit Beispielen.

Das Objekt folgt dem gleichen Konzept wie das der Karte, dh es wird ein Schlüssel-Wert-Paar zum Speichern von Daten verwendet. Es gibt jedoch geringfügige Unterschiede, die Map in bestimmten Situationen zu einem besseren Darsteller machen.

Map- ist eine Datenstruktur, die beim Speichern der Daten in Form von Paaren hilft. Das Paar besteht aus einem eindeutigen Schlüssel und einem dem Schlüssel zugeordneten Wert. Es hilft, Doppelspurigkeit zu vermeiden.

Hauptunterschiede

  • Die Karte ist eine Instanz eines Objekts, aber umgekehrt ist dies nicht der Fall.

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

  • In Object ist der Datentyp des Schlüsselfelds auf Ganzzahlen, Zeichenfolgen und Symbole beschränkt. Während in Map das Schlüsselfeld von einem beliebigen Datentyp sein kann (Ganzzahl, Array, Objekt)

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)

  • In der Karte bleibt die ursprüngliche Reihenfolge der Elemente erhalten. Dies gilt nicht für Objekte.

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.