JavaScript 是否保证对象属性顺序?

IT技术 javascript object
2021-01-07 22:13:03

如果我创建一个这样的对象:

var obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";

生成的对象总是这样吗?

{ prop1 : "Foo", prop2 : "Bar" }

也就是说,属性是否与我添加它们的顺序相同?

6个回答

自 ES2015 以来,对象的迭代顺序遵循一组特定规则,但它并不(总是)遵循插入顺序简单地说,迭代顺序是字符串键的插入顺序和数字键的升序的组合:

// key order: 1, foo, bar
const obj = { "foo": "foo", "1": "1", "bar": "bar" }

使用数组或Map对象可能是实现此目的的更好方法。Map有一些相似之处Object保证按插入顺序迭代键,无一例外:

Map 中的键是有序的,而添加到对象的键则不是。因此,当迭代它时,一个 Map 对象按插入的顺序返回键。(请注意,在 ECMAScript 2015 规范中,对象确实保留了字符串和符号键的创建顺序,因此遍历具有 ie 仅字符串键的对象将按插入顺序生成键)

请注意,在 ES2015 之前,根本无法保证对象中的属性顺序。ECMAScript 第三版中的对象定义(pdf)

4.3.3 对象

对象是对象类型的成员。它是一个无序的属性集合,每个属性都包含一个原始值、对象或函数。存储在对象属性中的函数称为方法。

“整数键”是否正确?根据我的有限测试,只有整数键/非负整数键按升序排序。
2021-02-12 22:13:03
整数键的行为在所有浏览器中并不一致。一些较旧的浏览器按插入顺序(使用字符串键)迭代整数键,而一些按升序迭代。
2021-02-20 22:13:03
@DaveDopson - 是的 - 过时的浏览器不遵循当前规范,因为它们没有更新。
2021-02-23 22:13:03

是(对于非整数键)。

大多数浏览器将对象属性迭代为:

  1. 按升序排列的整数键(以及像“1”这样解析为整数的字符串)
  2. 字符串键,按插入顺序(ES2015 保证这一点,所有浏览器都遵守)
  3. 符号名称,按插入顺序排列(ES2015 保证这一点并且所有浏览器都遵守)

一些较旧的浏览器结合了类别 #1 和 #2,按插入顺序迭代所有键。如果您的键可能会解析为整数,最好不要依赖任何特定的迭代顺序。

当前语言规范(自 ES2015 起)的插入顺序被保留,但解析为整数的键(例如“7”或“99”)除外,其中的行为因浏览器而异。例如,当键被解析为数字时,Chrome/V8 不遵守插入顺序。

旧语言规范(ES2015 之前):迭代顺序在技术上未定义,但所有主要浏览器都遵守 ES2015 行为。

请注意,ES2015 行为是由现有行为驱动的语言规范的一个很好的例子,而不是相反。要更深入地了解这种向后兼容的心态,请参阅http://code.google.com/p/v8/issues/detail?id=164,这是一个 Chrome 错误,其中详细介绍了 Chrome 迭代顺序行为背后的设计决策. 根据对该错误报告的(相当固执的)评论之一:

标准始终遵循实现,这就是 XHR 的来源,而 Google 通过实现 Gears 然后采用等效的 HTML5 功能来做同样的事情。正确的解决方法是让 ECMA 将事实上的标准行为正式合并到规范的下一个版本中。

@mik01aj 想从 React 链接源代码吗?(是的,我很懒)
2021-02-08 22:13:03
@BenjaminGruenbaum - 这正是我的观点。截至 2014 年,所有主要供应商都有一个共同的实施,因此标准最终将遵循(即在 2015 年)。
2021-02-13 22:13:03
@DaveDopson 对于第 1 点。它只是非负整数键正确吗?
2021-02-17 22:13:03
顺便说一句:React createFragmentAPI已经依赖于此了......🤔
2021-03-02 22:13:03
@BenjaminGruenbaum 您的评论是错误的。在 ES2015 中,仅对选定的方法保证顺序请参阅下面ftor 的答案
2021-03-06 22:13:03

普通对象中的属性顺序是 JavaScript 中的一个复杂主题。

虽然在 ES5 中没有明确指定顺序,但 ES2015 在某些情况下定义了一个顺序,并且此后对规范的连续更改越来越多地定义了顺序(甚至,从 ES2020 开始,for-in循环的顺序)。给定的是以下对象:

const o = Object.create(null, {
  m: {value: function() {}, enumerable: true},
  "2": {value: "2", enumerable: true},
  "b": {value: "b", enumerable: true},
  0: {value: 0, enumerable: true},
  [Symbol()]: {value: "sym", enumerable: true},
  "1": {value: "1", enumerable: true},
  "a": {value: "a", enumerable: true},
});

这导致以下顺序(在某些情况下):

Object {
  0: 0,
  1: "1",
  2: "2",
  b: "b",
  a: "a",
  m: function() {},
  Symbol(): "sym"
}

“拥有”(非继承)属性的顺序是:

  1. 按升序排列的类似整数的键
  2. 按插入顺序排列的字符串键
  3. 按插入顺序排列的符号

因此,有三个段,它们可能会改变插入顺序(如示例中发生的那样)。并且类似整数的键根本不遵守插入顺序。

在 ES2015 中,只有某些方法遵循以下顺序:

  • 对象分配
  • Object.defineProperties
  • Object.getOwnPropertyNames
  • Object.getOwnPropertySymbols
  • 反映.ownKeys
  • JSON.parse
  • JSON.stringify

从 ES2020 开始,所有其他人都这样做(一些在 ES2015 和 ES2020 之间的规范中,其他在 ES2020 中),其中包括:

  • Object.keys, Object.entries, Object.values, ...
  • 对于..in

最难确定的是for-in,它独特地包括继承的属性。在 ES2020 中完成的(除了边缘情况外)。链接(现已完成)提案中的以下列表提供了未指定顺序的边缘情况:

  • 被迭代的对象或其原​​型链中的任何东西都不是代理、类型化数组、module命名空间对象或宿主外来对象。
  • 在迭代过程中,对象及其原型链中的任何东西都没有原型更改。
  • 在迭代期间,对象及其原型链中的任何内容都没有删除属性。
  • 对象原型链中的任何内容都没有在迭代过程中添加的属性。
  • 在迭代过程中,对象或其原​​型链中的任何属性都不会发生可枚举性变化。
  • 没有不可枚举的属性会影响可枚举的属性。

结论:即使在 ES2015 中,您也不应该依赖 JavaScript 中普通对象的属性顺序。它很容易出错。如果您需要有序命名对,请Map改用,它纯粹使用插入顺序。如果您只需要订单,请使用数组或Set(也使用纯插入顺序)。

在撰写本文时,大多数浏览器确实以与插入相同的顺序返回属性,但它明确不能保证行为,因此不应该依赖。

ECMAScript规范常说的:

枚举属性的机制和顺序......没有指定。

但是在 ES2015 和更高版本中,非整数键将按插入顺序返回。

Chrome 实现与其他浏览器不同的顺序。请参阅code.google.com/p/v8/issues/detail?id=164
2021-02-09 22:13:03
这个答案在 ES2015 中是错误的。
2021-02-09 22:13:03
@Veverke 对订单没有明确的保证,因此应该始终假设订单实际上是随机的。
2021-02-17 22:13:03
@Veverke 不,订单不像可预测的那样。它依赖于实现并且随时可能更改,并且可能会在每次浏览器更新时更改(例如)。
2021-02-25 22:13:03
Opera 10.50 及更高版本,以及 IE9,匹配 Chrome 的顺序。Firefox 和 Safari 现在是少数(并且它们都对对象/数组使用不同的顺序)。
2021-03-03 22:13:03

这整个答案是在规范合规性的背景下,而不是任何引擎在特定时刻或历史上所做的。

一般来说,没有

实际问题非常模糊。

属性是否与我添加它们的顺序相同

在什么情况下?

答案是:这取决于许多因素。一般来说,没有

有时是的

在这里,您可以指望 plain 的属性键顺序Objects

  • 符合 ES2015 的引擎
  • 自有物业
  • Object.getOwnPropertyNames(), Reflect.ownKeys(),Object.getOwnPropertySymbols(O)

在所有情况下,这些方法都包括由[[OwnPropertyKeys]](见下文)指定的不可枚举的属性键和顺序键它们包含的键值类型不同(String和/或Symbol)。在此上下文中String包括整数值。

Object.getOwnPropertyNames(O)

返回O自己的String键控属性(属性名称)。

Reflect.ownKeys(O)

返回O自己的String- 和 -Symbol键控属性。

Object.getOwnPropertySymbols(O)

返回O自己的Symbol键控属性。

[[OwnPropertyKeys]]

顺序本质上是:Strings升序为整数Strings,创建顺序为非整数,创建顺序为符号。根据哪个函数调用它,其中一些类型可能不包括在内。

具体语言是key按以下顺序返回:

  1. ... [正在迭代的对象] 的每个自己的属性键PO它是一个整数索引,按数字索引升序排列

  2. ......每一个自己的财产关键PO是一个字符串,但不是整数指数,物业创建顺序

  3. ......每一个自己的财产关键PO是一个符号,在属性创建顺序

Map

如果您对有序映射感兴趣,您应该考虑使用MapES2015 中引入类型而不是普通的Objects