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

IT技术 javascript ecmascript-6
2021-02-04 20:40:56

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?
3个回答

注:由于ES2020的,喜欢更老的操作for-inObject.keys必须遵守财产秩序。这并没有改变这样一个事实,即对基本程序逻辑使用属性顺序可能不是一个好主意,因为非整数索引属性的顺序取决于属性的创建时间。


ES2015-ES2019 的答案:

对于for-in, Object.keys, 和JSON.stringify:不。

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

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

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

O的 [[Enumerate]] 内部方法被调用时,采取以下步骤:

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

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

还可以看到以下引用Object.keys

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

这意味着实现不需要定义特定的枚举顺序ECMAScript 2015 语言规范的项目编辑 Allen Wirfs-Brock 在规范完成后发表的一篇文章中证实一点

其他操作,如Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.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]] 来获取属性,然后对它们进行排序

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

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

值得注意的是,虽然规范不要求for-inObject.keys遵循顺序,但当前版本的 Firefox、Chrome 和 Edge 都这样做:jsfiddle.net/arhbn3k2/1这是有道理的,有多个枚举实现会很奇怪. 规范没有要求它,因为不同的引擎已经具有与新定义的顺序不同的野外行为,并且委员会不希望要求实现可能破坏现有代码。不过,这些实现似乎已经决定了;Firefox 的顺序肯定是不同的。
2021-03-22 20:40:56
@user10089632 哈哈!好问题……答案可能应该破坏的旧代码可以正常工作……至少在这方面!我想,这可能会导致异常没有在应该抛出的时候抛出,并完全改变行为。我怀疑更新的规范是否或可能永远像在所有情况下一样谨慎。
2021-04-02 20:40:56
@ 52d6c6af -这是在正确的ES2015,具体而言,并不需要Object.keys和这样的跟随新秩序。ES2020现在确实需要它是不正确的,尤其因为引擎已经更新(请参阅我在 2017 年的评论)这样做。由于 Oriol 不再对 SO 做出贡献,因此我在答案的顶部添加了注释。
2021-04-04 20:40:56
我实际上找不到 getOwnPropertyNames 如何保证订单的信息?实际上,Firefox 和 Chrome 返回的Object.getOwnPropertyNames({ 20 : 'a', 10 : 'b' })[ "10", "20" ]数字顺序,而不是书面顺序。
2021-04-08 20:40:56
只是好奇订单要求for-inObject.keys将是遗留兼容性问题如何?
2021-04-08 20:40:56

作为覆盖对方的回答,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]],这是它的迭代顺序内部方法指定。

有一些奇怪的情况目前的实现同意,在这种情况下,结果顺序将继续是未指定的,但这种情况很少见。

这个问题是关于 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(). 在后一种情况下,它可能是词法的或来自输入对象,但它必须吗?
2021-03-14 20:40:56
@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.xbeing true)。:-)
2021-03-28 20:40:56