Garantiert JavaScript die Reihenfolge der Objekteigenschaften?

646

Wenn ich ein Objekt wie dieses erstelle:

var obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";

Wird das resultierende Objekt immer so aussehen?

{ prop1 : "Foo", prop2 : "Bar" }

Das heißt, werden die Eigenschaften in der Reihenfolge sein, in der ich sie hinzugefügt habe?

Mellowsoon
quelle
1
Mögliches Duplikat der
Elementreihenfolge
1
@TJCrowder Könnten Sie etwas genauer erläutern, warum die akzeptierte Antwort nicht mehr korrekt ist? Die Frage, die Sie verlinkt haben, scheint auf die Idee zurückzuführen zu sein, dass die Bestellung von Immobilien gemäß Spezifikation immer noch nicht garantiert ist.
Null298
2
@ zero298: Die akzeptierte Antwort auf diese Frage beschreibt klar die angegebene Eigenschaftsreihenfolge ab ES2015 +. Legacy-Operationen ( for-in, Object.keys) müssen es nicht (offiziell) unterstützen, aber es gibt jetzt Ordnung. (Inoffiziell: Firefox, Chrome und Edge folgen alle der angegebenen Reihenfolge, auch in for-in und Object.keys, wo sie nicht offiziell verpflichtet sind: jsfiddle.net/arhbn3k2/1 )
TJ Crowder

Antworten:

473

Die Iterationsreihenfolge für Objekte folgt seit ES2015 einem bestimmten Regelwerk , folgt jedoch (immer) nicht der Einfügereihenfolge . Einfach ausgedrückt ist die Iterationsreihenfolge eine Kombination aus der Einfügereihenfolge für Zeichenfolgenschlüssel und der aufsteigenden Reihenfolge für nummerähnliche Schlüssel:

// key order: 1, foo, bar
const obj = { "foo": "foo", "1": "1", "bar": "bar" }

Die Verwendung eines Arrays oder eines MapObjekts kann ein besserer Weg sein, um dies zu erreichen. Mapteilt einige Ähnlichkeiten mit Objectund garantiert, dass die Schlüssel ausnahmslos in der Reihenfolge des Einfügens iteriert werden :

Die Schlüssel in Map sind geordnet, Schlüssel, die dem Objekt hinzugefügt wurden, nicht. Wenn ein Map-Objekt darüber iteriert, gibt es die Schlüssel in der Reihenfolge des Einfügens zurück. (Beachten Sie, dass in ECMAScript 2015-Spezifikationsobjekten die Erstellungsreihenfolge für Zeichenfolgen- und Symbolschlüssel beibehalten wird, sodass das Durchlaufen eines Objekts mit nur Zeichenfolgenschlüsseln Schlüssel in der Reihenfolge des Einfügens ergibt.)

Hinweis: Die Reihenfolge der Eigenschaften in Objekten wurde vor ES2015 überhaupt nicht garantiert. Definition eines Objekts aus ECMAScript Third Edition (pdf) :

4.3.3 Objekt

Ein Objekt ist ein Mitglied vom Typ Objekt. Es ist eine ungeordnete Sammlung von Eigenschaften, von denen jede einen primitiven Wert, ein Objekt oder eine Funktion enthält. Eine in einer Eigenschaft eines Objekts gespeicherte Funktion wird als Methode bezeichnet.

bpierre
quelle
Das Verhalten von Ganzzahlschlüsseln ist nicht in allen Browsern konsistent. Einige ältere Browser iterieren Ganzzahlschlüssel in Einfügereihenfolge (mit den Zeichenfolgenschlüsseln) und andere in aufsteigender Reihenfolge.
Dave Dopson
1
@ DaveDopson - Richtig - veraltete Browser folgen nicht der aktuellen Spezifikation, da sie nicht aktualisiert werden.
TJ Crowder
199

JA (für nicht ganzzahlige Schlüssel).

Die meisten Browser iterieren Objekteigenschaften wie folgt:

  1. Ganzzahlschlüssel in aufsteigender Reihenfolge (und Zeichenfolgen wie "1", die als Ints analysiert werden)
  2. Zeichenfolgenschlüssel in Einfügereihenfolge (ES2015 garantiert dies und alle Browser erfüllen die Anforderungen)
  3. Symbolnamen in Einfügereihenfolge (ES2015 garantiert dies und alle Browser erfüllen die Anforderungen)

Einige ältere Browser kombinieren die Kategorien 1 und 2 und wiederholen alle Schlüssel in der Einfügereihenfolge. Wenn Ihre Schlüssel möglicherweise als Ganzzahlen analysiert werden, sollten Sie sich nicht auf eine bestimmte Iterationsreihenfolge verlassen.

Die Einfügereihenfolge der aktuellen Sprachspezifikation (seit ES2015) bleibt erhalten, außer bei Schlüsseln, die als Ganzzahlen analysiert werden (z. B. "7" oder "99"), bei denen das Verhalten zwischen den Browsern variiert. Beispielsweise berücksichtigt Chrome / V8 die Einfügereihenfolge nicht, wenn die Tasten als numerisch analysiert werden.

Alte Sprachspezifikation (vor ES2015) : Die Iterationsreihenfolge war technisch nicht definiert, aber alle gängigen Browser entsprachen dem ES2015-Verhalten.

Beachten Sie, dass das ES2015-Verhalten ein gutes Beispiel dafür war, dass die Sprachspezifikation durch vorhandenes Verhalten gesteuert wird und nicht umgekehrt. Weitere Informationen zu dieser Abwärtskompatibilitäts-Denkweise finden Sie unter http://code.google.com/p/v8/issues/detail?id=164 , einem Chrome-Fehler, der die Entwurfsentscheidungen für das Verhalten der Chrome-Iterationsreihenfolge ausführlich behandelt . Gemäß einem der (eher meinungsgebundenen) Kommentare zu diesem Fehlerbericht:

Standards folgen immer Implementierungen, daher stammt XHR, und Google macht dasselbe, indem es Gears implementiert und dann die entsprechenden HTML5-Funktionen einsetzt. Die richtige Lösung besteht darin, dass ECMA das De-facto-Standardverhalten formell in die nächste Version der Spezifikation einbezieht.

Dave Dopson
quelle
2
@BenjaminGruenbaum - das war genau mein Punkt. Ab 2014 hatten alle großen Anbieter eine gemeinsame Implementierung und somit werden die Standards schließlich folgen (dh im Jahr 2015).
Dave Dopson
2
Übrigens: React createFragmentAPI verlässt sich bereits darauf ... 🤔
mik01aj
7
@BenjaminGruenbaum Dein Kommentar ist falsch. In ES2015 ist die Bestellung nur für ausgewählte Methoden garantiert . Siehe Antwort von ftor unten.
Piotr Dobrogost
82

Die Eigenschaftsreihenfolge in normalen Objekten ist ein komplexes Thema in Javascript.

Während in ES5 explizit keine Reihenfolge angegeben wurde, hat ES2015 in bestimmten Fällen eine Reihenfolge. Gegeben ist folgendes Objekt:

o = Object.create(null, {
  m: {value: function() {}, enumerable: true},
  "2": {value: "2", enumerable: true},
  "b": {value: "b", enumerable: true},
  0: {value: 0, enumerable: true},
  [Symbol()]: {value: "sym", enumerable: true},
  "1": {value: "1", enumerable: true},
  "a": {value: "a", enumerable: true},
});

Dies führt in der folgenden Reihenfolge (in bestimmten Fällen):

Object {
  0: 0,
  1: "1",
  2: "2",
  b: "b",
  a: "a",
  m: function() {},
  Symbol(): "sym"
}
  1. Ganzzahlige Schlüssel in aufsteigender Reihenfolge
  2. normale Schlüssel in Einfügereihenfolge
  3. Symbole in Einfügereihenfolge

Somit gibt es drei Segmente, die die Einfügereihenfolge ändern können (wie im Beispiel geschehen). Und ganzzahlige Schlüssel halten sich überhaupt nicht an die Einfügereihenfolge.

Die Frage ist, für welche Methoden diese Reihenfolge in der ES2015-Spezifikation garantiert ist.

Die folgenden Methoden garantieren die angezeigte Reihenfolge:

  • Object.assign
  • Object.defineProperties
  • Object.getOwnPropertyNames
  • Object.getOwnPropertySymbols
  • Reflect.ownKeys

Die folgenden Methoden / Schleifen garantieren überhaupt keine Bestellung:

  • Object.keys
  • für in
  • JSON.parse
  • JSON.stringify

Fazit: Auch in ES2015 sollten Sie sich nicht auf die Eigenschaftsreihenfolge normaler Objekte in Javascript verlassen. Es ist fehleranfällig. Verwenden Sie Mapstattdessen.

evolutionxbox
quelle
Ich habe die Schlussfolgerung in Knoten v8.11 grob getestet und sie ist korrekt.
merlin.ye
1
@BenjaminGruenbaum irgendwelche Gedanken?
Evolutionxbox
Object.entries garantieren die Reihenfolge gemäß der Ganzzahlregel
Mojimi
1
+1 für "Auch in ES2015 sollten Sie sich nicht auf die Eigenschaftsreihenfolge normaler Objekte in Javascript verlassen. Es ist fehleranfällig. Verwenden Sie stattdessen Map." Sie können sich nicht auf die Bestellung von Immobilien verlassen, wenn die Einhaltung der Vorschriften kompliziert ist. Wie testen Sie dies überhaupt auf Abwärtsvergleichbarkeit, wenn Sie ältere Browser unterstützen müssen?
Null298
1
Gibt es eine offizielle Dokumentation oder eine Referenz?
Schlage den
66

Zum Zeitpunkt des Schreibens gaben die meisten Browser Eigenschaften in der Reihenfolge zurück, in der sie eingefügt wurden, aber es war ausdrücklich kein garantiertes Verhalten, daher sollte man sich nicht darauf verlassen.

In der ECMAScript-Spezifikation heißt es:

Die Mechanik und Reihenfolge der Aufzählung der Eigenschaften ... ist nicht angegeben.

In ES2015 und höher werden jedoch nicht ganzzahlige Schlüssel in Einfügereihenfolge zurückgegeben.

Alnitak
quelle
16
Chrome implementiert eine andere Reihenfolge als andere Browser. Siehe code.google.com/p/v8/issues/detail?id=164
Tim Down
9
Opera 10.50 und höher sowie IE9 entsprechen der Reihenfolge von Chrome. Firefox und Safari sind jetzt eine Minderheit (und beide verwenden auch unterschiedliche Ordnungen für Objekte / Arrays).
gsnedders
1
@Veverke Es gibt ausdrücklich keine Garantie für die Bestellung, daher sollte man immer davon ausgehen, dass die Bestellung tatsächlich zufällig ist.
Alnitak
1
@Veverke Nein, die Reihenfolge ist nicht so vorhersehbar. Es ist implementierungsabhängig und kann jederzeit geändert werden. Es kann sich (zum Beispiel) jedes Mal ändern, wenn sich Ihr Browser selbst aktualisiert.
Alnitak
3
Diese Antwort ist in ES2015 falsch.
Benjamin Gruenbaum
42

Diese ganze Antwort steht im Zusammenhang mit der Einhaltung von Spezifikationen, nicht mit dem, was ein Motor zu einem bestimmten Zeitpunkt oder in der Vergangenheit tut.

Im Allgemeinen nein

Die eigentliche Frage ist sehr vage.

Werden die Eigenschaften in der Reihenfolge sein, in der ich sie hinzugefügt habe?

In welchem ​​Kontext?

Die Antwort lautet: Es hängt von einer Reihe von Faktoren ab. Im Allgemeinen nein .

Manchmal ja

Hier können Sie sich auf die Reihenfolge der Eigenschaftsschlüssel für Plain verlassen Objects:

  • ES2015-konformer Motor
  • Eigene Immobilien
  • Object.getOwnPropertyNames(), Reflect.ownKeys(),Object.getOwnPropertySymbols(O)

In allen Fällen umfassen diese Methoden nicht aufzählbare Eigenschaftsschlüssel und Bestellschlüssel, wie von angegeben [[OwnPropertyKeys]](siehe unten). Sie unterscheiden sich in der Art der Schlüsselwerte, die sie enthalten ( Stringund / oder Symbol). StringEnthält in diesem Zusammenhang ganzzahlige Werte.

Object.getOwnPropertyNames(O)

Gibt Odie StringEigenschaften mit eigenen Schlüsseln ( Eigenschaftsnamen ) zurück.

Reflect.ownKeys(O)

Gibt Odie eigenen Eigenschaften mit Stringund ohne SymbolSchlüssel zurück.

Object.getOwnPropertySymbols(O)

Gibt Odie SymbolEigenschaften mit eigenen Schlüsseln zurück.

[[OwnPropertyKeys]]

Die Reihenfolge ist im Wesentlichen: ganzzahlig Stringsin aufsteigender Reihenfolge, nicht ganzzahlig Stringsin Erstellungsreihenfolge, Symbole in Erstellungsreihenfolge. Abhängig davon, welche Funktion dies aufruft, sind einige dieser Typen möglicherweise nicht enthalten.

Die spezifische Sprache ist, dass Schlüssel in der folgenden Reihenfolge zurückgegeben werden:

  1. ... jeder eigene Eigenschaftsschlüssel Pvon O[dem zu iterierenden Objekt], der ein ganzzahliger Index ist, in aufsteigender numerischer Indexreihenfolge

  2. ... jeder eigener Eigenschaftsschlüssel Pvon Odem ist ein String , aber kein ganze Zahl Index, in Immobilien Schöpfungsordnung

  3. ... jeder eigener Eigenschaftsschlüssel Pvon Odem ist ein Symbol, in Immobilien Schöpfungsordnung

Map

Wenn Sie an geordneten Karten interessiert sind, sollten Sie den Mapin ES2015 eingeführten Typ anstelle von einfach verwenden Objects.

JMM
quelle
13

In modernen Browsern können Sie die MapDatenstruktur anstelle eines Objekts verwenden.

Entwickler Mozilla> Karte

Ein Map-Objekt kann seine Elemente in Einfügereihenfolge durchlaufen ...

Topicus
quelle
7

In ES2015 ist dies der Fall, aber nicht so, wie Sie vielleicht denken

Die Reihenfolge der Schlüssel in einem Objekt wurde erst in ES2015 garantiert. Es wurde implementierungsdefiniert.

Doch in ES2015 in wurde angegeben. Wie viele Dinge in JavaScript wurde dies aus Kompatibilitätsgründen durchgeführt und spiegelte im Allgemeinen einen bestehenden inoffiziellen Standard bei den meisten JS-Engines wider (wobei Sie wissen, wer eine Ausnahme darstellt).

Die Reihenfolge wird in der Spezifikation unter der abstrakten Operation OrdinaryOwnPropertyKeys definiert , die alle Methoden zum Iterieren über die eigenen Schlüssel eines Objekts unterstützt. Umschrieben lautet die Reihenfolge wie folgt:

  1. Alle ganzzahligen Index Tasten (Sachen wie "1123", "55"usw.) in numerischer Reihenfolge aufsteigend.

  2. Alle Zeichenfolgenschlüssel, die keine ganzzahligen Indizes sind, in der Reihenfolge ihrer Erstellung (älteste zuerst).

  3. Alle Symbolschlüssel in der Reihenfolge ihrer Erstellung (älteste zuerst).

Es ist albern zu sagen, dass die Reihenfolge unzuverlässig ist - es ist zuverlässig, es ist wahrscheinlich nicht das, was Sie wollen, und moderne Browser implementieren diese Reihenfolge korrekt.

Einige Ausnahmen umfassen Methoden zum Auflisten geerbter Schlüssel, z. B. die for .. inSchleife. Die for .. inSchleife garantiert keine Reihenfolge gemäß der Spezifikation.

GregRos
quelle
7

Ab ES2015 ist die Eigenschaftsreihenfolge für bestimmte Methoden garantiert, die über Eigenschaften iterieren. aber nicht andere . Leider werden im Allgemeinen am häufigsten Methoden verwendet, für die keine Bestellung garantiert ist:

  • Object.keys, Object.values,Object.entries
  • for..in Schleifen
  • JSON.stringify

Aber, wie der ES2020, Eigentum , um für diese bisher nicht vertrauenswürdig Methoden werden durch die Spezifikation garantiert wird in dem gleichen deterministisch werden iteriert wie die andere, aufgrund zum fertigen Vorschlag: für-in Mechanik .

Genau wie bei den Methoden mit einer garantierten Iterationsreihenfolge (wie Reflect.ownKeysund Object.getOwnPropertyNames) werden auch die zuvor nicht angegebenen Methoden in der folgenden Reihenfolge iteriert:

  • Numerische Array-Schlüssel in aufsteigender numerischer Reihenfolge
  • Alle anderen Nicht-Symbol-Schlüssel in Einfügereihenfolge
  • Symbolschlüssel in Einfügereihenfolge

Dies ist, was so ziemlich jede Implementierung bereits tut (und dies seit vielen Jahren tut), aber der neue Vorschlag hat es offiziell gemacht.

Obwohl die aktuelle Spezifikation für ... in Iterationsreihenfolge " fast völlig nicht spezifiziert , sind echte Engines tendenziell konsistenter:"

Der Mangel an Spezifität in ECMA-262 spiegelt nicht die Realität wider. In jahrelangen Diskussionen haben Implementierer festgestellt, dass das Verhalten von For-In einige Einschränkungen aufweist, denen jeder folgen muss, der Code im Web ausführen möchte.

Da jede Implementierung bereits vorhersehbar über Eigenschaften iteriert, kann sie in die Spezifikation aufgenommen werden, ohne die Abwärtskompatibilität zu beeinträchtigen.


Es gibt einige seltsame Fälle, über die sich Implementierungen derzeit nicht einig sind, und in solchen Fällen wird die resultierende Reihenfolge weiterhin nicht spezifiziert. Damit die Bestellung der Immobilie garantiert wird :

Weder das iterierte Objekt noch irgendetwas in seiner Prototypkette ist ein Proxy, ein typisiertes Array, ein Modul-Namespace-Objekt oder ein exotisches Host-Objekt.

Weder das Objekt noch irgendetwas in seiner Prototypkette hat seinen Prototyp während der Iteration geändert.

Weder das Objekt noch irgendetwas in seiner Prototypkette hat eine Eigenschaft, die während der Iteration gelöscht wird.

Nichts in der Prototypenkette des Objekts hat eine Eigenschaft, die während der Iteration hinzugefügt wurde.

Bei keiner Eigenschaft des Objekts oder irgendetwas in seiner Prototypkette ändert sich die Aufzählbarkeit während der Iteration.

Keine nicht aufzählbare Eigenschaft beschattet eine aufzählbare.

Bestimmte Leistung
quelle
5

Wie bereits erwähnt, haben Sie keine Garantie für die Reihenfolge, wenn Sie die Eigenschaften eines Objekts durchlaufen. Wenn Sie eine geordnete Liste mit mehreren Feldern benötigen, habe ich vorgeschlagen, ein Array von Objekten zu erstellen.

var myarr = [{somfield1: 'x', somefield2: 'y'},
{somfield1: 'a', somefield2: 'b'},
{somfield1: 'i', somefield2: 'j'}];

Auf diese Weise können Sie eine reguläre for-Schleife verwenden und die Einfügereihenfolge festlegen. Sie können dann die Array-Sortiermethode verwenden, um diese bei Bedarf in ein neues Array zu sortieren.

Drew Durham
quelle
3

Ich habe es gerade auf die harte Tour herausgefunden.

Bei Verwendung von Reagieren mit Redux wird der Statuscontainer, dessen Schlüssel ich durchlaufen möchte, um untergeordnete Elemente zu generieren, bei jeder Änderung des Speichers aktualisiert (gemäß den Unveränderlichkeitskonzepten von Redux).

Also, um zu nehmen, habe Object.keys(valueFromStore)ich verwendet Object.keys(valueFromStore).sort(), so dass ich zumindest jetzt eine alphabetische Reihenfolge für die Schlüssel habe.

kriskate
quelle
-7

Aus dem JSON-Standard :

Ein Objekt ist eine ungeordnete Sammlung von null oder mehr Name / Wert-Paaren, wobei ein Name eine Zeichenfolge und ein Wert eine Zeichenfolge, eine Zahl, ein Boolescher Wert, eine Null, ein Objekt oder ein Array ist.

(Hervorhebung von mir).

Nein, Sie können die Bestellung nicht garantieren.

Kevin Lacquement
quelle
7
Dies wird durch den ECMAScript-Standard festgelegt - nicht durch die JSON-Spezifikation.
Alnitak
7
@Alnitak, @Iacqui: JSON übernimmt dies nur aus der ECMAScript-Spezifikation. Es ist auch für JSON spezifiziert, aber dies bezieht sich nicht wirklich auf die Frage.
Paŭlo Ebermann