Czy JavaScript gwarantuje porządek właściwości obiektu?

647

Jeśli utworzę taki obiekt:

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

Czy wynikowy obiekt zawsze będzie tak wyglądał?

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

Czy właściwości będą w tej samej kolejności, w której je dodałem?

łagodny
źródło
1
@TJCrowder Czy mógłbyś bardziej szczegółowo wyjaśnić, dlaczego zaakceptowana odpowiedź nie jest już dokładna? Pytanie, które połączyłeś, wydaje się sprowadzać do idei, że porządek nieruchomości nadal nie jest gwarantowany według specyfikacji.
zero298
2
@ zero298: Przyjęta odpowiedź na to pytanie jasno opisuje określony porządek właściwości według ES2015 +. Starsze operacje ( for-in, Object.keys) nie muszą go obsługiwać (oficjalnie), ale teraz jest zamówienie. (Nieoficjalnie: Firefox, Chrome i Edge zachowują określoną kolejność nawet w for-in i Object.keys, gdzie oficjalnie nie są wymagane: jsfiddle.net/arhbn3k2/1 )
TJ Crowder

Odpowiedzi:

474

Kolejność iteracji obiektów jest zgodna z pewnym zestawem reguł od ES2015, ale nie (zawsze) jest zgodna z kolejnością wstawiania . Mówiąc najprościej, kolejność iteracji jest kombinacją kolejności wstawiania kluczy ciągów i porządku rosnącego dla kluczy podobnych do liczb:

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

Użycie tablicy lub Mapobiektu może być lepszym sposobem na osiągnięcie tego. Mapdzieli pewne podobieństwa Objecti gwarantuje, że klucze będą iterowane w kolejności wstawiania , bez wyjątku:

Klucze w Mapie są uporządkowane, a klucze dodane do obiektu nie są. Zatem podczas iteracji nad nim obiekt Map zwraca klucze w kolejności wstawiania. (Należy zauważyć, że w specyfikacji ECMAScript 2015 obiekty zachowują porządek tworzenia kluczy ciągów i symboli, więc przejście do obiektu za pomocą np. Tylko kluczy ciągów dałoby klucze w kolejności wstawiania)

Uwaga: kolejność właściwości w obiektach nie była w ogóle gwarantowana przed ES2015. Definicja obiektu z ECMAScript wydanie trzecie (pdf) :

4.3.3 Obiekt

Obiekt jest członkiem typu Object. Jest to nieuporządkowany zbiór właściwości, z których każda zawiera pierwotną wartość, obiekt lub funkcję. Funkcja przechowywana we właściwości obiektu nazywa się metodą.

bpierre
źródło
Zachowanie kluczy całkowitych nie jest spójne we wszystkich przeglądarkach. Niektóre starsze przeglądarki iterują klucze całkowite w kolejności wstawiania (z kluczami ciągów), a niektóre w kolejności rosnącej.
Dave Dopson
1
@DaveDopson - Prawo - przestarzałe przeglądarki nie są zgodne z bieżącą specyfikacją, ponieważ nie są aktualizowane.
TJ Crowder
199

TAK (dla kluczy niecałkowitych).

Większość przeglądarek iteruje właściwości obiektu jako:

  1. Klucze całkowite w kolejności rosnącej (i ciągi takie jak „1”, które analizują jako liczby całkowite)
  2. Klucze ciągów w kolejności wstawiania (ES2015 gwarantuje to i wszystkie przeglądarki są zgodne)
  3. Nazwy symboli w kolejności wstawiania (ES2015 gwarantuje to i wszystkie przeglądarki są zgodne)

Niektóre starsze przeglądarki łączą kategorie 1 i 2, iterując wszystkie klucze w kolejności wstawiania. Jeśli twoje klucze mogą być analizowane jako liczby całkowite, najlepiej nie polegać na żadnej określonej kolejności iteracji.

Bieżąca specyfikacja języka (od ES2015) jest zachowywana, z wyjątkiem kluczy analizowanych jako liczby całkowite (np. „7” lub „99”), w których zachowanie jest różne w różnych przeglądarkach. Na przykład Chrome / V8 nie przestrzega kolejności wstawiania, gdy klucze są analizowane jako numeryczne.

Specyfikacja w starym języku (przed ES2015) : Kolejność iteracji była technicznie niezdefiniowana, ale wszystkie główne przeglądarki były zgodne z zachowaniem ES2015.

Należy zauważyć, że zachowanie ES2015 było dobrym przykładem specyfikacji języka opartej na istniejącym zachowaniu, a nie odwrotnie. Aby lepiej zrozumieć sposób myślenia o zgodności wstecznej, zobacz http://code.google.com/p/v8/issues/detail?id=164 , błąd Chrome, który szczegółowo omawia decyzje projektowe dotyczące zachowania kolejności iteracji w Chrome . Według jednego (raczej upartego) komentarza do tego raportu o błędzie:

Standardy zawsze podążają za implementacjami, stąd XHR, a Google robi to samo, wdrażając wtyczkę Gears, a następnie obejmując równoważną funkcjonalność HTML5. Właściwą poprawką jest formalne włączenie przez ECMA de facto standardowego zachowania do następnej wersji specyfikacji.

Dave Dopson
źródło
2
@BenjaminGruenbaum - dokładnie o to mi chodziło. Począwszy od 2014 r. Wszyscy główni dostawcy mieli wspólną implementację, w związku z czym normy będą ostatecznie przestrzegane (tj. W 2015 r.).
Dave Dopson
2
Nawiasem mówiąc: React createFragmentAPI już polega na tym ... 🤔
mik01aj
7
@BenjaminGruenbaum Twój komentarz jest fałszywy. W ES2015 kolejność jest gwarantowana tylko dla wybranych metod. Patrz odpowiedź z ftor poniżej.
Piotr Dobrogost
82

Kolejność właściwości w normalnych obiektach jest złożonym tematem w Javascript.

Podczas gdy w ES5 wyraźnie nie określono kolejności, w niektórych przypadkach ES2015 ma zamówienie. Podany jest następujący obiekt:

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},
});

Powoduje to następującą kolejność (w niektórych przypadkach):

Object {
  0: 0,
  1: "1",
  2: "2",
  b: "b",
  a: "a",
  m: function() {},
  Symbol(): "sym"
}
  1. klucze całkowite w porządku rosnącym
  2. normalne klucze w kolejności wprowadzania
  3. Symbole w kolejności wstawiania

Istnieją zatem trzy segmenty, które mogą zmienić kolejność wstawiania (jak to miało miejsce w przykładzie). A klucze podobne do liczb całkowitych wcale nie trzymają się kolejności wstawiania.

Pytanie brzmi: dla jakich metod to zamówienie jest gwarantowane w specyfikacji ES2015?

Następujące metody gwarantują pokazaną kolejność:

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

Następujące metody / pętle nie gwarantują żadnego zamówienia:

  • Object.keys
  • dla w
  • JSON.parse
  • JSON.stringify

Wniosek: Nawet w ES2015 nie powinieneś polegać na kolejności właściwości normalnych obiektów w JavaScript. Jest podatny na błędy. Użyj Mapzamiast tego.

evolutionxbox
źródło
Z grubsza przetestowałem wniosek w węźle v8.11 i jest on poprawny.
merlin.ye
1
@BenjaminGruenbaum jakieś myśli?
evolutionxbox
Object.entries gwarantuje porządek zgodnie z zasadą całkowitą
Mojimi,
1
+1 dla „Nawet w ES2015 nie powinieneś polegać na kolejności właściwości normalnych obiektów w Javascript. Jest podatny na błędy. Zamiast tego użyj mapy.”. Nie można polegać na zamówieniu nieruchomości, gdy zgodność jest skomplikowana. Jak w tym celu przetestować porównywalność wsteczną, jeśli trzeba obsługiwać starsze przeglądarki?
zero298,
1
Czy jest na to oficjalna dokumentacja lub odniesienie?
Pokonaj
66

W chwili pisania tego tekstu większość przeglądarek zwróciła właściwości w tej samej kolejności, w jakiej zostały wstawione, ale nie było to wyraźnie gwarantowane, więc nie można było na nim polegać.

Specyfikacja ECMAScript mawiała:

Mechanika i kolejność wyliczania właściwości ... nie jest określona.

Jednak w ES2015 i późniejszych kluczach niecałkowitych zostaną zwrócone w kolejności wstawiania.

Alnitak
źródło
16
Chrome implementuje inną kolejność niż inne przeglądarki. Zobacz code.google.com/p/v8/issues/detail?id=164
Tim Down
9
Opera 10.50 i nowsze, a także IE9, odpowiadają kolejności Chrome. Firefox i Safari są teraz mniejszością (i oba używają również różnych zamówień dla obiektów / tablic).
gsnedders
1
@ Veverke nie ma wyraźnej gwarancji na zamówienie, dlatego zawsze należy zakładać, że zamówienie jest faktycznie losowe.
Alnitak,
1
@Veverke Nie, kolejność nie jest taka przewidywalna. Jest to zależne od implementacji i może ulec zmianie w dowolnym momencie i może ulec zmianie (na przykład) przy każdej aktualizacji przeglądarki.
Alnitak,
3
Ta odpowiedź jest nieprawidłowa w ES2015.
Benjamin Gruenbaum
42

Cała odpowiedź jest w kontekście zgodności specyfikacji, a nie tego, co robi dany silnik w danym momencie lub historycznie.

Ogólnie nie

Rzeczywiste pytanie jest bardzo niejasne.

czy właściwości będą w tej samej kolejności, w której je dodałem

W jakim kontekście?

Odpowiedź brzmi: zależy to od wielu czynników. Ogólnie nie .

Czasem tak

Oto, gdzie możesz liczyć na kolejność kluczy właściwości dla zwykłego Objects:

  • Silnik zgodny z ES2015
  • Własne nieruchomości
  • Object.getOwnPropertyNames(), Reflect.ownKeys(),Object.getOwnPropertySymbols(O)

We wszystkich przypadkach metody te obejmują niepoliczalne klucze właściwości i klucze zamówień określone przez [[OwnPropertyKeys]](patrz poniżej). Różnią się rodzajem kluczowych wartości, które zawierają ( Stringi / lub Symbol). W tym kontekście Stringobejmuje wartości całkowite.

Object.getOwnPropertyNames(O)

Zwraca Owłasne Stringwłaściwości klucza ( nazwy właściwości ).

Reflect.ownKeys(O)

Zwraca Owłasne właściwości Stringi Symbolklucze.

Object.getOwnPropertySymbols(O)

Zwraca Owłasne Symbolwłaściwości.

[[OwnPropertyKeys]]

Kolejność jest zasadniczo: podobna do liczby całkowitej Stringsw porządku rosnącym, niecałkowita Stringsw kolejności tworzenia, Symbole w kolejności tworzenia. W zależności od tego, która funkcja to wywołuje, niektóre z tych typów mogą nie zostać uwzględnione.

W konkretnym języku klucze są zwracane w następującej kolejności:

  1. ... każdy własny klucz właściwości P z O[istota obiekt potwierdził], który jest indeksem całkowitą, w rosnącej kolejności numerycznej indeks

  2. ... każdy własny klucz właściwości P stanowi O, że jest String, ale nie jest indeksem całkowitą, w celu stworzenia własności

  3. ... każdy własny klucz nieruchomość Pstanowi O, że jest symbolem, w celu stworzenia własności

Map

Jeśli interesują Cię uporządkowane mapy, powinieneś rozważyć użycie Maptypu wprowadzonego w ES2015 zamiast zwykłego Objects.

JMM
źródło
13

W nowoczesnych przeglądarkach możesz używać Mapstruktury danych zamiast obiektu.

Deweloper mozilla> Mapa

Obiekt Map może iterować swoje elementy w kolejności wstawiania ...

Temat
źródło
7

W ES2015 tak jest, ale nie do tego, co myślisz

Kolejność kluczy w obiekcie nie była gwarantowana do ES2015. Zostało zdefiniowane w ramach implementacji.

Jednak w ES2015 był określona. Podobnie jak wiele rzeczy w JavaScript, zostało to zrobione dla celów kompatybilności i ogólnie odzwierciedlało istniejący nieoficjalny standard wśród większości silników JS (z wyjątkiem Ciebie).

Kolejność jest zdefiniowana w specyfikacji w ramach abstrakcyjnej operacji OrdinaryOwnPropertyKeys , która stanowi podstawę wszystkich metod iteracji po własnych kluczach obiektu. Parafrazując, kolejność jest następująca:

  1. Wszystkie indeksy całkowitą klucze (rzeczy jak "1123", "55"itp) w rosnącej kolejności numerycznej.

  2. Wszystkie klucze łańcuchowe, które nie są indeksami liczb całkowitych, w kolejności tworzenia (najstarsze-pierwsze).

  3. Wszystkie klucze symboli, w kolejności tworzenia (od najstarszej do pierwszej).

Głupio jest powiedzieć, że zamówienie jest niewiarygodne - jest niezawodne, po prostu prawdopodobnie nie jest to, czego chcesz, a nowoczesne przeglądarki poprawnie realizują to zamówienie.

Niektóre wyjątki obejmują metody wyliczania odziedziczonych kluczy, takie jak for .. inpętla. for .. inPętli nie gwarantuje zlecenie według specyfikacji.

GregRos
źródło
7

Począwszy od ES2015, porządek właściwości jest gwarantowany dla niektórych metod, które iterują właściwości. ale nie inni . Niestety najczęściej stosowane są metody, które nie mają gwarancji zamówienia:

  • Object.keys, Object.values,Object.entries
  • for..in pętle
  • JSON.stringify

Jednak od ES2020 porządek właściwości dla tych wcześniej niewiarygodnych metod będzie gwarantowany przez iterację specyfikacji w taki sam deterministyczny sposób jak inne, ze względu na gotową propozycję: mechanika in-in .

Podobnie jak w przypadku metod, które mają gwarantowaną kolejność iteracji (jak Reflect.ownKeysi Object.getOwnPropertyNames), nieokreślone wcześniej metody będą również iterować w następującej kolejności:

  • Klawisze numeryczne w porządku rosnącym
  • Wszystkie inne klucze niebędące symbolami, w kolejności wstawiania
  • Klucze symboli, w kolejności wstawiania

To właśnie robi prawie każde wdrożenie (i robiło to przez wiele lat), ale nowa propozycja uczyniła to oficjalnym.

Chociaż obecna specyfikacja pozostawia… w kolejności iteracji „ prawie zupełnie nieokreślone , prawdziwe silniki są bardziej spójne:”

Brak specyficzności w ECMA-262 nie odzwierciedla rzeczywistości. W trakcie dyskusji z ostatnich lat, implementatorzy zauważyli, że istnieją pewne ograniczenia w zachowaniu for-in, których musi przestrzegać każdy, kto chce uruchomić kod w sieci.

Ponieważ każda implementacja już iteracyjnie iteruje właściwości, można ją wprowadzić do specyfikacji bez naruszania kompatybilności wstecznej.


Istnieje kilka dziwnych przypadków, na które obecnie nie zgadzają się wdrożenia , i w takich przypadkach wynikowe zamówienie będzie nadal nieokreślone. Aby zagwarantować porządek nieruchomości :

Ani obiekt, który jest iterowany, ani nic w łańcuchu prototypowym nie jest serwerem proxy, tablicą maszynową, obiektem w przestrzeni nazw modułów ani obiektem egzotycznym hosta.

Ani obiekt, ani nic w jego łańcuchu prototypów nie zmieniło prototypu podczas iteracji.

Ani obiekt, ani nic w jego łańcuchu prototypów nie ma właściwości usuwanej podczas iteracji.

Nic w łańcuchu prototypów obiektu nie ma właściwości dodanej podczas iteracji.

Żadna właściwość obiektu ani nic w jego łańcuchu prototypów nie zmienia swojej wyliczalności podczas iteracji.

Żadna właściwość, której nie można wyliczyć, przesłania tę właściwość.

CertainPerformance
źródło
5

Jak powiedzieli inni, nie masz żadnej gwarancji co do kolejności, gdy iterujesz właściwości obiektu. Jeśli potrzebujesz uporządkowanej listy wielu pól, zasugerowałem utworzenie tablicy obiektów.

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

W ten sposób możesz użyć zwykłej pętli for i mieć kolejność wstawiania. W razie potrzeby można użyć metody sortowania Array, aby posortować ją do nowej tablicy.

Drew Durham
źródło
3

Właśnie to odkryłem na własnej skórze.

Używając React with Redux, którego kontener stanu, który chcę przechodzić w celu wygenerowania potomków, jest odświeżany przy każdej zmianie sklepu (zgodnie z koncepcjami niezmienności Redux).

Tak więc, aby wziąć Object.keys(valueFromStore), użyłem Object.keys(valueFromStore).sort(), że przynajmniej teraz mam alfabetyczną kolejność klawiszy.

kriskate
źródło
-7

Ze standardu JSON :

Obiekt to nieuporządkowana kolekcja zer lub więcej par nazwa / wartość, gdzie nazwa jest łańcuchem, a wartością jest łańcuch, liczba, wartość logiczna, null, obiekt lub tablica.

(moje podkreślenie).

Więc nie, nie możesz zagwarantować zamówienia.

Kevin Lacquement
źródło
7
jest to określone przez standard ECMAScript - nie specyfikację JSON.
Alnitak,
7
@Alnitak, @Iacqui: JSON bierze to tylko ze specyfikacji ECMAScript. Jest on również określony dla JSON, ale tak naprawdę nie dotyczy to pytania.
Paŭlo Ebermann