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

Does ES6 introduce a well-defined order of enumeration for object properties?

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?

注意: 从 ES2020 开始,甚至 for-inObject.keys 等较旧的操作也需要遵循 属性 顺序。这并没有改变这样一个事实,即对基本程序逻辑使用 属性 顺序可能不是一个好主意,因为非整数索引属性的顺序取决于属性的创建时间。


ES2015-ES2019 答案:

对于for-inObject.keysJSON.stringify没有

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

虽然 ES6 / ES2015 添加了 属性 顺序,但由于遗留兼容性问题,它不需要 for-inObject.keysJSON.stringify 来遵循该顺序。

for-in loops iterate according to [[Enumerate]],定义为(强调我的):

When the [[Enumerate]] internal method of O is called the following steps are taken:

Return an Iterator object (25.1.1.2) whose next method iterates over all the String-valued keys of enumerable properties of O. The Iterator object must inherit from %IteratorPrototype% (25.1.2). The mechanics and order of enumerating the properties is not specified but must conform to the rules specified below [1].

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

另请参阅 Object.keys 中的这句话:

If an implementation defines a specific order of enumeration for the for-in statement, [...]

这意味着 实现不需要定义特定的枚举顺序。此 has been confirmed 由 ECMAScript 2015 语言规范的项目编辑 Allen Wirfs-Brock 在规范完成后 post 制作。

其他操作,如Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.defineProperties, and Reflect.ownKeys对普通对象遵循以下顺序:

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

此行为在 [[OwnPropertyKeys]] internal method. But certain exotic objects 中定义的内部方法略有不同。例如,Proxy 的 ownKeys 陷阱可能 return 任何顺序的数组:

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


[1] 下面写着:

[[Enumerate]] must obtain the own property keys of the target object as if by calling its [[OwnPropertyKeys]] internal method.

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

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

in the same relative order as would be produced by the Iterator that would be returned if the [[Enumerate]] internal method was invoked

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

如另一个答案所述,ES2015 没有为(非常常用的)属性 迭代方法 for-inObject.keysJSON.stringify 定义枚举顺序,而它 确实 Reflect.ownKeys 等其他方法定义了枚举方法。 不过,这种不一致性很快就会不复存在,所有属性迭代方法将以可预测的方式迭代。

正如许多人可能在他们自己的 JS 经验和评论中观察到的那样,尽管上述这些方法的规范不保证 属性 迭代顺序,但每个实现几乎总是以相同的确定顺序迭代反正。因此,有一个(已完成的)提案来更改规范以使此行为正式化:

Specifying for-in enumeration order (Stage 4)

有了这个提议,在大多数情况下,for..inObject.keys / values / entriesJSON.stringify 可以保证按顺序迭代:

(1) 数字数组键

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

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

这与 Reflect.ownKeys 的顺序相同,并且已经保证以这种方式迭代的其他方法。

specification text is rather simple: EnumerateObjectProperties, the problematic abstract method invoked by for..in, etc, whose order used to be unspecified, will now call [[OwnPropertyKeys]],这是指定迭代顺序 的内部方法。

有一些奇怪的情况,目前实现同意,在这种情况下,结果顺序将continue be unspecified,但这种情况很少而且相差甚远。

这个问题是关于 EcmaScript 2015 (ES6) 的。但需要注意的是EcmaScript2017规范有removed the following paragraph that previously appeared in the specification for Object.keys, here quoted from the EcmaScript 2016 specification:

If an implementation defines a specific order of enumeration for the for-in statement, the same order must be used for the elements of the array returned in step 3.

此外,EcmaScript 2020 规范removes the following paragraph from the section on EnumerableOwnPropertyNames, which still appears in the EcmaScript 2019 specification:

  1. Order the elements of properties so they are in the same relative order as would be produced by the Iterator that would be returned if the EnumerateObjectProperties internal method were invoked with O.

这些删除意味着从 EcmaScript 2020 开始,Object.keys 强制执行与 Object.getOwnPropertyNamesReflect.ownKeys 相同的特定顺序,即 OrdinaryOwnPropertyKeys 中指定的顺序。顺序是:

  1. 拥有数组索引1的属性,按数字索引升序排列
  2. 其他自己的String属性,按属性创建时间升序排列
  3. 拥有 Symbol 属性,按 属性 创建的时间升序排列

1 array index 是一个字符串值 属性 键,它是一个规范的数字字符串2其数值 i 是 +0 ≤ i < 232 - 1 范围内的整数。

2 canonical numeric String 是由 ToString 或字符串“-0”生成的数字的字符串表示形式。因此,例如,“012”不是规范的数字字符串,但“12”是。

应该注意的是,所有主要的实施都在多年前就已与此顺序保持一致。