【问题标题】:Does ES6 introduce a well-defined order of enumeration for object properties?ES6 是否为对象属性引入了明确定义的枚举顺序?
【发布时间】:2015-07-16 13:25:25
【问题描述】:

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?

【问题讨论】:

  • 顺便说一句,对于Object.getOwnPropertyNamesObject.getOwnPropertySymbolsReflect.ownKeys,顺序定义的。
  • 实际上 - 答案又是 - 不再是最新的 :) ES2016 引入了 Object.keysfor.. in 循环的迭代顺序和规范:19.1.2.16 (Object.keys) 调用 7.3。 21 (EnumerateOwnProperties) 反过来保证:“对属性的元素进行排序,使其与迭代器生成的相对顺序相同,如果使用 O 调用 EnumerateObjectProperties 内部方法,则返回该迭代器。” - EnumerateOwnProperties 反过来保证 [[OwnPropertyKeys]] (9.1.11) 保证顺序的 9.1.11.1 (ordinaryownpropertykeys)。
  • 这些数字来自 ES2017 规范 (8),可以在这里免费找到:ecma-international.org/ecma-262/8.0
  • @BenjaminGruenbaum 我看不出13.7.5.15 EnumerateObjectProperties 在哪里保证与[[OwnPropertyKeys]] 相同的顺序。它只说“...必须通过调用[the] internal method来获取自己的属性键[...]”。获取后如何处理它们,或者它们如何与继承的属性合并,留给实现。

标签: javascript ecmascript-6


【解决方案1】:

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


ES2015-ES2019的答案:

对于for-inObject.keysJSON.stringify否。

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

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

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

O的[[Enumerate]]内部方法调用如下 已采取措施:

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

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

还可以看到Object.keys的这句话:

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

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

对于普通对象,Object.getOwnPropertyNamesObject.getOwnPropertySymbolsObject.definePropertiesReflect.ownKeys 等其他操作请遵循以下顺序:

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

此行为在[[OwnPropertyKeys]] 内部方法中定义。但是某些exotic objects 对该内部方法的定义略有不同。例如,Proxy 的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]] 内部方法,则会返回

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

【讨论】:

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

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

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

Specifying for-in enumeration order (Stage 4)

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

(1) 数字数组键

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

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

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

specification text 比较简单:EnumerateObjectPropertiesfor..in 调用的有问题的抽象方法等,其顺序使用 未指定,will now 调用[[OwnPropertyKeys]],即是指定迭代顺序的内部方法

目前存在一些实现同意的奇怪情况,在这种情况下,生成的订单将continue be unspecified,但这种情况很少见。

【讨论】:

    【解决方案3】:

    这个问题是关于 EcmaScript 2015 (ES6)。但需要注意的是,EcmaScript2017 规范中有removed 之前出现在Object.keys 规范中的以下段落,这里引用自the EcmaScript 2016 specification

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

    此外,EcmaScript 2020 规范 removes 来自 EnumerableOwnPropertyNames 部分的以下段落,仍然出现在 in the EcmaScript 2019 specification

    1. properties 的元素进行排序,使其与使用 O 调用 EnumerateObjectProperties 内部方法时返回的迭代器生成的相对顺序相同.

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

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

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

    2canonical numeric String 是由 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)。 :-)
    猜你喜欢
    • 2011-04-18
    • 2022-12-17
    • 1970-01-01
    • 2017-12-18
    • 1970-01-01
    • 2017-03-05
    • 1970-01-01
    相关资源
    最近更新 更多