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


647

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

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

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

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

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





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. Это неупорядоченный набор свойств, каждое из которых содержит примитивное значение, объект или функцию. Функция, хранящаяся в свойстве объекта, называется методом.


Поведение целочисленных клавиш не одинаково во всех браузерах. Некоторые старые браузеры выполняют итерацию целочисленных ключей в порядке вставки (со строковыми ключами), а некоторые - в порядке возрастания.
— Дейв Допсон

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вместо этого.


Я грубо проверил заключение в узле 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.


13

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

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

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


7

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

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

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

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

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

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

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

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

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


7

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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(), так что у меня по крайней мере теперь есть алфавитный порядок для ключей.


-7

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

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

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

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


7
это определяется стандартом ECMAScript, а не спецификацией JSON.
— Альнитак

7
@Alnitak, @Iacqui: JSON берет это только из спецификации ECMAScript. Он также указан для JSON, но на самом деле это не относится к вопросу.
— Paŭlo Ebermann
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.