ES6是否为对象属性引入了定义明确的枚举顺序?


71

ES6是否为对象属性引入了定义明确的枚举顺序?

var o = {
  '1': 1,
  'a': 2,
  'b': 3
}

Object.keys(o); // ["1", "a", "b"] - is this ordering guaranteed by ES6?

for(let k in o) {
  console.log(k);
} // 1 2 3 - is this ordering guaranteed by ES6?


2
顺便说一句,对于Object.getOwnPropertyNamesObject.getOwnPropertySymbolsReflect.ownKeys顺序定义。
Bergi 2015年

6
实际上-答案再次出现-不再是最新的了:) ES2016引入了forObject.keysfor.. inloops的迭代顺序,以及规范:19.1.2.16(Object.keys)调用7.3.21(EnumerateOwnProperties),这又保证:“对元素的顺序进行排序属性,因此它们的迭代顺序与Iterator产生的相对顺序相同,如果用O调用EnumerateObjectProperties内部方法,则会返回该迭代器。[[OwnPropertyKeys]]-EnumerateOwnProperty依次执行保证(9.1.11)和9.1.11.1(ordinaryownpropertykeys)来保证顺序。
本杰明·格伦鲍姆

2
这些数字来自ES2017规范(8),可在此处免费找到:ecma-international.org/ecma-262/8.0
Benjamin Gruenbaum

5
@BenjaminGruenbaum我看不到13.7.5.15 EnumerateObjectProperties在哪里保证与[[OwnPropertyKeys]]相同的顺序。它只说“ ...必须通过调用[the]内部方法来获得自己的属性键[...] ”。获取后如何处理它们,或如何将它们与继承的属性合并,留给实现。
Bergi

Answers:


85

注意:从ES2020开始,甚至更旧的操作(例如for-in和)Object.keys都必须遵循属性顺序。这不会改变以下事实:使用基本程序逻辑的属性顺序可能不是一个好主意,因为非整数索引属性的顺序取决于创建属性的时间。


ES2015-ES2019的答案:

对于for-inObject.keysJSON.stringify

对于一些其他操作:是的,通常。

虽然ES6 / ES2015增加了财产秩序,它不需要for-inObject.keys或者JSON.stringify遵循这一顺序,由于传统的兼容性问题。

for-in根据[[Enumerate]]循环循环,[[定义为(强调我的):

调用O的[[Enumerate]]内部方法时,将执行以下步骤:

返回一个Iterator对象(25.1.1.2),其下一个方法迭代O的所有可枚举属性的String值键。Iterator对象必须继承%IteratorPrototype%(25.1.2)。没有指定枚举属性的机制和顺序,但必须符合下面[1]中指定的规则。

ES7 / ES2016删除了[[Enumerate]]内部方法,而是使用了抽象操作EnumerateObjectProperties,但是就像[[Enumerate]]一样,它没有指定任何顺序。

也可以从查看此报价Object.keys

如果实现为for-in语句定义了特定的枚举顺序,则[...]

这意味着不需要实现来定义特定的枚举顺序。ECMAScript 2015语言规范的项目编辑Allen Wirfs-Brock在规范完成后发表的帖子中已确认了这一点

其他操作,如Object.getOwnPropertyNamesObject.getOwnPropertySymbolsObject.defineProperties,并Reflect.ownKeys做好后续普通物体下面的命令:

  1. 整数索引(如果适用),以升序排列。
  2. 其他字符串键(如果有),按属性创建顺序。
  3. 符号键(如果适用),按属性创建顺序。

此行为在[[OwnPropertyKeys]]内部方法中定义。但是某些特殊对象对内部方法的定义稍有不同。例如,代理的ownKeys陷阱可能以任何顺序返回数组:

console.log(Reflect.ownKeys(new Proxy({}, {
  ownKeys: () => ['3','1','2']
}))); // ['3','1','2'], the integer indices are not sorted!


[1]下面说:

[[Enumerate]]必须获得目标对象自己的属性键, 就像通过调用其[[OwnPropertyKeys]]内部方法一样。

并且[[OwnPropertyKeys]]的顺序是明确定义的。但是请不要让您感到困惑:“好像”仅表示“相同的属性”,而不是“相同的顺序”。

可以在EnumerableOwnNames中看到,它使用[[OwnPropertyKeys]]获取属性,然后对它们进行排序

如果调用[[Enumerate]]内部方法,则返回的顺序与Iterator产生的顺序相同。

如果要求[[Enumerate]]以与[[OwnPropertyKeys]]相同的顺序进行迭代,则无需重新排序。


1
我实际上找不到信息,如何确保getOwnPropertyNames确保订单?而在现实Firefox和Chrome返回与此Object.getOwnPropertyNames({ 20 : 'a', 10 : 'b' })[ "10", "20" ]数值排序,而不是书面命令。
Ciantic

3
只是好奇如何为一个订单需求for-inObject.keys将是一个传统的兼容性问题
user10089632

6
值得一提的是,虽然不需要for-inObject.keys不遵循该规范,但当前版本的Firefox,Chrome和Edge都可以:jsfiddle.net/arhbn3k2/1这很有意义,但是有多个枚举实现是很奇怪的。规范并不需要它,因为不同的引擎已经具有与新定义的顺序不同的狂妄行为,并且委员会不想要求实现可能破坏现有代码的实现。不过,这些实现似乎已经决定要这样做。Firefox的顺序肯定是不同的。
TJ Crowder

2
@ user10089632哈哈!很好的问题...答案可能应该破坏的旧代码可以正常运行...至少在这方面!我想,这可能导致异常没有在应有的时候被抛出,并完全改变了行为。我怀疑更新的规范是否或曾经会像在所有情况下一样严格。
麦克啮齿动物公司

2
@ 52d6c6af-正确的是,尤其是ES2015并不需要Object.keys遵循这样的新命令。这是不正确的,因为ES2020现在确实需要它,这尤其是因为引擎已经更新(请参阅我从2017年开始的评论)。由于Oriol不再对SO有所贡献,因此我在答案的顶部添加了注释。
TJ Crowder

10

作为覆盖对方的回答,ES2015不会为(很常用的)属性来定义枚举顺序迭代方法for-inObject.keysJSON.stringify,而它定义好其他方法,如枚举的方法Reflect.ownKeys但是,这种不一致很快将不再存在,并且所有属性迭代方法都将以可预测的方式进行迭代。

正如许多人在使用JS和注释中所观察到的那样,尽管上述方法的规范无法保证属性迭代的顺序,但是每个实现几乎总是以相同的确定性顺序进行迭代。结果,有一个(完成的)提案可以更改规范以使此行为正式生效:

指定for-in枚举顺序阶段4

这个建议,在大多数情况下,for..inObject.keys/ values/ entries,并JSON.stringify保证迭代,以便在:

(1)数字数组键

(2)非符号键,按插入顺序

(3)符号键,按插入顺序

Reflect.ownKeys保证其他方法迭代的顺序相同。

规范的文本是相当简单的:EnumerateObjectProperties通过调用的问题抽象方法for..in等,其顺序使用是未指定的,现在将调用[[OwnPropertyKeys]],这是它的迭代顺序内部方法指定。

当前有几种不同的实现尚无法达成共识的情况,在这种情况下,结果顺序将继续不确定,但这种情况很少。


7

此问题与EcmaScript 2015(ES6)有关。但应注意,EcmaScript2017规范删除了以前出现在的规范中的以下段落Object.keys,此处引用了EcmaScript 2016规范

如果实现为for-in语句定义了枚举的特定顺序,则必须对步骤3中返回的数组元素使用相同的顺序。

此外,EcmaScript 2020规范从的部分中删除了以下段落,该段落EnumerableOwnPropertyNames仍然出现在EcmaScript 2019规范中

  1. 属性的元素进行排序,以使它们具有与Iterator产生的相对顺序相同的相对顺序,如果使用O调用EnumerateObjectProperties内部方法,则会返回该迭代器。

这些清除意味着从EcmaScript的2020年起,Object.keys实施统一的具体顺序Object.getOwnPropertyNamesReflect.ownKeys,即在指定的OrdinaryOwnPropertyKeys。顺序是:

  1. 自己的属性是数组索引1在上升的数字顺序索引
  2. 其他自己的String属性,按属性创建的时间升序排列
  3. 自己的符号属性,以属性创建的时间顺序升序

1个一种数组索引是一个字符串值属性密钥是一个规范的数字串2,其数值是一个整数的范围0≤<2 32 - 1。

2规范数字串是通过将产生一个数字的字符串表示ToString,或字符串“-0”。因此,例如,“ 012”不是规范的数字字符串,而是“ 12”。

应该注意的是,所有主要的实现都已经在几年前与这个命令保持一致。


提出了一个问题,即用相同的操作插入属性时会得到什么顺序。例如,对象文字或Object.assign()。在后一种情况下,它可能是词法的或来自输入对象,但这是否必须?
罗伯特·西默

@RobertSiemer-规范中对此进行了明确定义:对象文字中的属性按源代码顺序添加(永远都是如此),并且Object.assign(和属性扩展)的行为遵循源对象的属性顺序。所以Object.keys({a:1,b:2})(现在)可靠,["a","b"]而且Object.keys({b:2,a:1})(现在)可靠["b","a"]。但是一般而言,最好不要依赖对象属性的迭代顺序。依托创建/分配的量级上是细(和共同:const copy = {...original, x: true};在可靠地产生的结果copy.xtrue)。:-)
TJ Crowder
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.