Гарантирует ли JavaScript порядок свойств объекта?

647

Если я создаю объект, как это:

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

Будет ли результирующий объект всегда выглядеть так?

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

То есть свойства будут в том же порядке, в котором я их добавил?

mellowsoon
источник
1
@TJCrowder Не могли бы вы рассказать немного подробнее о том, почему принятый ответ более не точен? Вопрос, который вы связали, кажется, сводится к идее, что порядок свойств по-прежнему не гарантирован.
ноль298
2
@ zero298: Принятый ответ на этот вопрос четко описывает указанный порядок свойств с ES2015 +. Устаревшие операции ( for-in, Object.keys) не должны поддерживать это (официально), но сейчас есть порядок. (Неофициально: Firefox, Chrome и Edge следуют указанному порядку даже в for-in и Object.keys, где они официально не обязаны: jsfiddle.net/arhbn3k2/1 )
TJ Crowder

Ответы:

474

Порядок итераций для объектов соответствует определенному набору правил, начиная с ES2015, но он не всегда соответствует порядку вставки . Проще говоря, порядок итераций представляет собой комбинацию порядка вставки для строковых ключей и возрастающего порядка для числовых ключей:

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

Использование массива или Mapобъекта может быть лучшим способом для достижения этой цели. Mapразделяет некоторые сходства Objectи гарантирует, что ключи будут повторяться в порядке вставки , без исключения:

Ключи в Map упорядочены, а ключи, добавленные к объекту - нет. Таким образом, при итерации по нему объект Map возвращает ключи в порядке вставки. (Обратите внимание, что в спецификациях ECMAScript 2015 объекты сохраняют порядок создания для строковых и символьных ключей, поэтому обход объекта с т. Е. Только строковыми ключами даст ключи в порядке вставки)

Как примечание, порядок свойств в объектах не был гарантирован до ES2015. Определение объекта из ECMAScript Third Edition (pdf) :

4.3.3 Объект

Объект является членом типа Object. Это неупорядоченный набор свойств, каждое из которых содержит примитивное значение, объект или функцию. Функция, хранящаяся в свойстве объекта, называется методом.

bpierre
источник
Поведение целочисленных клавиш не одинаково во всех браузерах. Некоторые старые браузеры выполняют итерацию целочисленных ключей в порядке вставки (со строковыми ключами), а некоторые - в порядке возрастания.
Дейв Допсон
1
@DaveDopson - справа - устаревшие браузеры не следуют текущей спецификации, потому что они не обновляются.
TJ Crowder
199

ДА (для нецелых ключей).

Большинство браузеров повторяют свойства объекта следующим образом:

  1. Целочисленные ключи в порядке возрастания (и такие строки, как «1», которые разбираются как целые)
  2. Строковые ключи, в порядке вставки (ES2015 гарантирует это, и все браузеры соответствуют)
  3. Имена символов в порядке вставки (ES2015 гарантирует это, и все браузеры соответствуют)

Некоторые старые браузеры объединяют категории # 1 и # 2, перебирая все ключи в порядке вставки. Если ваши ключи могут анализироваться как целые числа, лучше не полагаться на какой-либо определенный порядок итераций.

Текущие языковые спецификации (начиная с ES2015) порядок вставки сохраняется, за исключением случаев, когда ключи разбираются как целые числа (например, «7» или «99»), где поведение варьируется в разных браузерах. Например, Chrome / V8 не учитывает порядок вставки, когда ключи анализируются как числовые.

Спецификация старого языка (до ES2015) : Порядок итераций был технически не определен, но все основные браузеры соответствовали поведению ES2015.

Обратите внимание, что поведение ES2015 было хорошим примером языковой спецификации, обусловленной существующим поведением, а не наоборот. Чтобы получить более глубокое представление об этом образе мышления с обратной совместимостью, см. Http://code.google.com/p/v8/issues/detail?id=164 , ошибку Chrome, которая подробно описывает решения по разработке поведения порядка итерации Chrome , За один из (довольно самоуверенных) комментариев к этому сообщению об ошибке:

Стандарты всегда следуют реализациям, отсюда и XHR, и Google делает то же самое, внедряя Gears, а затем применяя эквивалентную функциональность HTML5. Правильное решение состоит в том, чтобы ECMA формально включила стандартное поведение де-факто в следующую версию спецификации.

Дейв Допсон
источник
2
@ BenjaminGruenbaum - это была моя точка зрения точно. По состоянию на 2014 год все основные поставщики имели общую реализацию, и, следовательно, стандарты будут в конечном итоге следовать (т.е. в 2015 году).
Дэйв Допсон
2
Кстати, React createFragmentAPI уже полагается на это ... 🤔
mik01aj
7
@BenjaminGruenbaum Ваш комментарий неверный. В ES2015 заказ гарантирован только для выбранных методов. См ответ на Ftor ниже.
Петр Доброгост
84

Порядок свойств в обычных объектах является сложным предметом в Javascript.

В то время как в ES5 порядок явно не указан, в некоторых случаях ES2015 имеет порядок. Дан следующий объект:

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

Это приводит к следующему порядку (в некоторых случаях):

Object {
  0: 0,
  1: "1",
  2: "2",
  b: "b",
  a: "a",
  m: function() {},
  Symbol(): "sym"
}
  1. целочисленные ключи в порядке возрастания
  2. нормальные ключи в порядке вставки
  3. Символы в порядке вставки

Таким образом, есть три сегмента, которые могут изменить порядок вставки (как это было в примере). И целочисленные ключи вообще не придерживаются порядка вставки.

Вопрос в том, для каких методов этот заказ гарантирован в спецификации ES2015?

Следующие методы гарантируют указанный порядок:

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

Следующие методы / циклы гарантируют отсутствие порядка вообще:

  • Object.keys
  • for..in
  • JSON.parse
  • JSON.stringify

Вывод: даже в ES2015 вы не должны полагаться на порядок свойств обычных объектов в Javascript. Это склонно к ошибкам. Используйте Mapвместо этого.

evolutionxbox
источник
Я грубо проверил заключение в узле v8.11, и это правильно.
merlin.ye
1
@BenjaminGruenbaum есть мысли?
evolutionxbox
Object.entries гарантирует порядок, следуя целочисленному правилу
Модзими
1
+1 за «Даже в ES2015 вы не должны полагаться на порядок свойств обычных объектов в Javascript. Он подвержен ошибкам. Вместо этого используйте Map.». Вы не можете полагаться на порядок собственности, когда соблюдение запутано. Как вы можете проверить это на обратную сопоставимость, если вам приходится поддерживать старые браузеры?
ноль298
1
Есть ли официальная документация об этом или ссылка?
Удар
66

На момент написания большинство браузеров возвращали свойства в том же порядке, в котором они были вставлены, но это явно не гарантированное поведение, поэтому на него не следует полагаться.

Спецификация ECMAScript используется, чтобы сказать:

Механика и порядок перечисления свойств ... не уточняется.

Однако в ES2015 и более поздних нецелые ключи будут возвращены в порядке вставки.

Альнитак
источник
16
Chrome реализует другой порядок для других браузеров. См. Code.google.com/p/v8/issues/detail?id=164
Тим Даун
9
Opera 10.50 и выше, а также IE9 соответствуют порядку Chrome. Firefox и Safari теперь составляют меньшинство (и оба они также используют разные порядки для объектов / массивов).
gsnedders
1
@ Veverke явно нет гарантии на заказ, поэтому всегда следует предполагать, что порядок фактически случайный.
Альнитак
1
@ Veverke Нет, порядок не такой уж предсказуемый. Он зависит от реализации и может быть изменен в любое время и может меняться (например) каждый раз, когда обновляется ваш браузер.
Альнитак
3
Этот ответ неверен в ES2015.
Бенджамин Грюнбаум
42

Весь этот ответ находится в контексте соответствия спецификациям, а не тому, что любой двигатель делает в определенный момент или исторически.

Как правило, нет

Фактический вопрос очень расплывчатый.

свойства будут в том же порядке, в котором я их добавил

В каком контексте?

Ответ: это зависит от ряда факторов. В общем нет .

Иногда да

Вот где вы можете рассчитывать на порядок ключей свойства для простого Objects:

  • ES2015-совместимый двигатель
  • Собственные свойства
  • Object.getOwnPropertyNames(), Reflect.ownKeys(),Object.getOwnPropertySymbols(O)

Во всех случаях эти методы включают не перечисляемые ключи свойств и ключи порядка, как указано [[OwnPropertyKeys]](см. Ниже). Они отличаются по типу ключевых значений, которые они включают ( Stringи / или Symbol). В этом контексте Stringвключены целочисленные значения.

Object.getOwnPropertyNames(O)

Возвращает Oсобственные Stringсвойства ключа ( имена свойств ).

Reflect.ownKeys(O)

Возвращает Oсобственные Stringи Symbolключевые свойства.

Object.getOwnPropertySymbols(O)

Возвращает Oсобственные Symbolсвойства ключа

[[OwnPropertyKeys]]

Порядок по существу: целочисленный Stringsв порядке возрастания, нецелыйStrings в порядке создания, символы в порядке создания. В зависимости от того, какая функция вызывает это, некоторые из этих типов могут быть не включены.

Определенным языком является то, что ключи возвращаются в следующем порядке:

  1. ... каждый собственный ключ свойства Pиз O[объекта итерируемых] , что является целочисленным индексом, по возрастанию числового порядка индексов

  2. ... каждый собственный ключ свойства Pв Oтом , что это строка , но не является целым индекс, в порядке создания собственности

  3. ... каждый собственный ключ свойства Pв Oтом , что это символ, для того создания имущества

Map

Если вас интересуют упорядоченные карты, вам следует рассмотреть возможность использования Mapтипа, представленного в ES2015, вместо простого Objects.

JMM
источник
13

В современных браузерах вы можете использовать Mapструктуру данных вместо объекта.

Разработчик mozilla> Карта

Объект Map может перебирать свои элементы в порядке вставки ...

Topicus
источник
7

В ES2015 это так, но не то, что вы думаете

Порядок ключей в объекте не гарантировался до ES2015. Это было определено реализацией.

Однако, в ES2015 ин был указан. Как и многие другие вещи в JavaScript, это было сделано в целях совместимости и, как правило, отражало существующий неофициальный стандарт среди большинства движков JS (за исключением, вы знаете, кто).

Порядок определяется в спецификации в рамках абстрактной операции OrdinaryOwnPropertyKeys , которая лежит в основе всех методов итерации по собственным ключам объекта. Перефразируя, порядок выглядит следующим образом:

  1. Все целочисленные индексные ключи (например "1123", "55"и т. Д.) В порядке возрастания номеров.

  2. Все строковые ключи, которые не являются целочисленными индексами, в порядке создания (самые старые-первые).

  3. Все символьные клавиши в порядке создания (самые старые-первые).

Глупо говорить, что порядок ненадежен - он надежен, просто, вероятно, это не то, что вам нужно, и современные браузеры правильно его реализуют.

Некоторые исключения включают методы перечисления унаследованных ключей, такие как for .. inцикл. for .. inЦикл не гарантирует порядок в соответствии со спецификацией.

GregRos
источник
7

Начиная с ES2015, порядок свойств гарантирован для определенных методов, которые перебирают свойства. но не другие . К сожалению, методы, порядок которых не гарантирован, обычно используются чаще всего:

  • Object.keys, Object.values,Object.entries
  • for..in петли
  • JSON.stringify

Но, начиная с ES2020, порядок свойств для этих ранее ненадежных методов будет гарантирован, что спецификация будет повторяться таким же детерминированным образом, как и другие, из-за готового предложения: механика for-in .

Как и в случае с методами, которые имеют гарантированный порядок итераций (например, Reflect.ownKeysи Object.getOwnPropertyNames), ранее неуказанные методы также будут выполнять итерацию в следующем порядке:

  • Ключи числового массива в порядке возрастания номеров
  • Все остальные не символьные ключи, в порядке вставки
  • Символьные клавиши, в порядке вставки

Это то, что в значительной степени каждая реализация уже делает (и делала в течение многих лет), но новое предложение сделало его официальным.

Хотя текущая спецификация оставляет для… в итерационном порядке « почти полностью неопределенные , реальные движки имеют тенденцию быть более последовательными:»

Отсутствие специфичности в ECMA-262 не отражает реальность. В ходе дискуссий, которые велись годами, разработчики заметили, что существуют некоторые ограничения на поведение for-in, которым должен следовать любой, кто хочет запускать код в сети.

Поскольку каждая реализация уже предсказуемо перебирает свойства, ее можно поместить в спецификацию, не нарушая обратной совместимости.


Есть несколько странных случаев, с которыми реализации в настоящее время не согласуются, и в таких случаях результирующий порядок будет по-прежнему не определен. Для гарантированного заказа недвижимости :

Ни итерируемый объект, ни что-либо в его цепочке прототипов не являются прокси, типизированным массивом, объектом пространства имен модуля или экзотическим объектом хоста.

Ни у объекта, ни у чего-либо в его цепочке прототипов нет изменений прототипа во время итерации.

Ни у объекта, ни у чего-либо в его цепочке прототипов нет свойства, удаленного во время итерации.

Ничто в цепочке прототипов объекта не имеет свойства, добавленного во время итерации.

Ни одно свойство объекта или чего-либо в его цепочке прототипов не может изменить свою перечислимость во время итерации.

Ни одно не перечислимое свойство не затеняет перечислимое.

CertainPerformance
источник
5

Как уже говорили другие, у вас нет никаких гарантий относительно порядка, когда вы перебираете свойства объекта. Если вам нужен упорядоченный список из нескольких полей, я предложил создать массив объектов.

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

Таким образом, вы можете использовать обычный цикл for и иметь порядок вставки. Затем вы можете использовать метод сортировки Array, чтобы отсортировать его в новый массив, если это необходимо.

Дрю Дарем
источник
3

Просто выяснил это трудным путем.

Используя React с Redux, контейнер состояний, ключи которого я хочу просмотреть для генерации дочерних элементов, обновляется каждый раз при изменении хранилища (согласно концепции неизменяемости Redux).

Таким образом, для того, чтобы взять, Object.keys(valueFromStore)я использовал Object.keys(valueFromStore).sort(), так что у меня по крайней мере теперь есть алфавитный порядок для ключей.

kriskate
источник
-7

Из стандарта JSON :

Объект - это неупорядоченная коллекция из нуля или более пар имя / значение, где имя - это строка, а значение - это строка, число, логическое значение, ноль, объект или массив.

(акцент мой).

Итак, нет, вы не можете гарантировать заказ.

Кевин лакмент
источник
7
это определяется стандартом ECMAScript, а не спецификацией JSON.
Альнитак
7
@Alnitak, @Iacqui: JSON берет это только из спецификации ECMAScript. Он также указан для JSON, но на самом деле это не относится к вопросу.
Paŭlo Ebermann