На порядок обхода свойств объектов в ES6 всё ещё нельзя положиться
Сегодня узнал, что текущая версия ECMAScript описывает порядок обхода свойств объектов.
В ES5 порядок обхода не был гарантирован. Это означало, что если есть:
const obj = { a: 1, b: 2, c: 3 }
и по нему итерируют с помощью for...in, нельзя было быть уверенным, что порядок будет a-b-c.
Но в ES6 зафиксировали «правильный» порядок. Он немного странный, но хоть какой-то. А потому Object.keys() или for...in на объекте из примере выше дадут ожидаемый порядок обхода — a-b-c.
Теперь усложним:
const obj = { a: 1, b: 2, d: 4 }
obj.c = 3
И если сделать:
> Object.keys(obj)
[ 'a', 'b', 'd', 'c' ]
Как видно, c идёт после d. Порядок добавления сохраняется.
Но добавим ещё свойства:
obj[5] = 5;
obj['6'] = 6;
obj[2 ** 32 - 2] = 7;
obj[2 ** 32 - 1] = 8;
obj[Symbol('9')] = 9;
obj.j = 10;
И посмотрим на результат:
> Object.keys(obj)
[
'5',
'6',
'4294967294',
'a',
'b',
'd',
'c',
'4294967295',
'j',
]
Какое-то месиво.
Дело в том, что по спецификации обход собственных свойств объекта происходит так:
-
Сначала идут ключи, похожие на индексы массива, в порядке возрастания. Да, ключи объекта — это строки, но некоторые строки похожи на числа, потому выглядят как индексы массива.
-
Затем обычные строковые ключи в порядке добавления.
-
Затем
Symbol-ключи в порядке добавления.
Однако, есть два нюанса в примере выше:
-
4294967294считается индексом, а4294967295уже нет — потому что граница «похожих на индексы ключей» ограничена2^32 - 1(не включительно). Всё, что выше, — считается обычной строкой. -
Symbol-свойства не попадают вObject.keys,JSON.stringify,for...inи т. д. Если нужно их получить — используетсяReflect.ownKeys:> Reflect.ownKeys(obj) [ '5', '6', '4294967294', 'a', 'b', 'd', 'c', '4294967295', 'j', Symbol(9), ]
И последнее:
Когда объект выводится в консоль без Object.keys или подобных «обёрток», порядок может отличаться в зависимости от окружения, потому что обход по объекту в этом случае не обязательно происходит в порядке, описанном спецификацией. Не говоря уже о том, что браузеры сперва показывают «превью» объекта в консоли, и только по клику — полную версию; и даже эти два отображения могут отличаться.
Иными словами:
-
Не стоит доверять порядку ключей в выводе объектов в консоли.
-
И даже несмотря на спецификацию, не стоит полагаться на порядок обхода там, где это критично.
Старая, но до сих пор актуальная статья по теме — The traversal order of object properties in ES6.