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


647

如果我创建这样的对象:

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

生成的对象会总是这样吗?

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

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





1
@TJCrowder您能否详细说明为什么接受的答案不再准确?您链接的问题似乎可以归结为仍然无法按规范保证财产顺序的想法。
zero298 '17

2
@ zero298:对该问题的可接受答案清楚地描述了ES2015 +起的指定属性顺序。传统业务(for-inObject.keys)没有支持它(官方),但也有为了现在。(非正式地:Firefox,Chrome和Edge都遵循指定的顺序,即使在for-in和Object.keys中,它们也没有正式要求:jsfiddle.net/arhbn3k2/1
TJ Crowder

Answers:


474

自ES2015起,对象的迭代顺序遵循一组特定的规则,但不(始终)遵循插入顺序。简而言之,迭代顺序是字符串键的插入顺序和数字键的升序的组合:

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

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

Map中的键是有序的,而添加到对象中的键则没有顺序。因此,在对其进行迭代时,Map对象将按插入顺序返回键。(请注意,在ECMAScript 2015规范中,对象确实保留了字符串键和符号键的创建顺序,因此仅通过字符串键遍历对象即会按插入顺序产生键)

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

4.3.3对象

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


整数键的行为在所有浏览器中均不一致。一些较旧的浏览器按插入顺序(带有字符串键)迭代整数键,而另一些则按升序迭代。
戴夫·多普森

1
@DaveDopson-对-过时的浏览器不遵循当前规范,因为它们没有更新。
TJ Crowder

199

是(用于非整数键)。

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

  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将事实上的标准行为正式纳入规范的下一个修订版。


2
@BenjaminGruenbaum-正是我的意思。截至2014年,所有主要供应商都采用了统一的实施方式,因此最终将遵循这些标准(即2015年)。
戴夫·多普森

2
顺便说一句:阵营createFragmentAPI已经依赖于这个...🤔
mik01aj

7
@BenjaminGruenbaum您的评论是错误的。在ES2015中,仅针对所选方法保证顺序。请参阅下面的ftor 答案
Piotr Dobrogost

82

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

虽然在ES5中明确未指定任何顺序,但在某些情况下ES2015具有顺序。给定以下对象:

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
  • Reflect.ownKeys

下列方法/循环完全不保证顺序:

  • 对象键
  • 在..in
  • JSON.parse
  • JSON.stringify

结论:即使在ES2015中,您也不应依赖Javascript中普通对象的属性顺序。容易出错。使用Map代替。


我粗略测试了节点v8.11中的结论,它是正确的。
merlin.ye18年

1
@BenjaminGruenbaum有什么想法吗?
evolutionxbox

遵循整数规则的Object.entries保证顺序
Mojimi

1
+1表示“即使在ES2015中,您也不应该依赖Javascript中普通对象的属性顺序。它容易出错,请改用Map。”。当合规性变得复杂时,您不能依靠财产秩序。如果您必须支持较旧的浏览器,您甚至该如何进行向后可比性测试?
zero298 '18 / 12/12

1
是否有任何官方文件或参考资料?
击败

66

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

ECMAScript规范常说的:

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

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


16
Chrome与其他浏览器的实现顺序不同。请参阅code.google.com/p/v8/issues/detail?id=164
Tim Down

9
Opera 10.50及更高版本以及IE9与Chrome的订单匹配。Firefox和Safari现在是少数(它们对对象/数组的使用顺序也不同)。
gsnedders 2011年

1
@Veverke显然不能保证顺序,因此应始终假定顺序实际上是随机的。
Alnitak

1
@Veverke不,顺序与可预测的一样。它依赖于实现,并且随时可能更改,并且可能会在每次浏览器更新时更改(例如)。
Alnitak'3

3
在ES2015中,此答案是错误的。
本杰明·格伦鲍姆

42

整个答案是在符合规范的情况下进行的,而不是任何引擎在特定时刻或历史上所做的事情。

通常没有

实际问题很模糊。

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

在什么情况下?

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

有时候是

在这里,您可以依靠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-keyed属性。

Object.getOwnPropertySymbols(O)

返回O自己的Symbol键属性。

[[OwnPropertyKeys]]

顺序本质上是:Strings升序类似整数,非整数类似Strings,创建次序,符号创建次序。根据哪个函数调用此函数,可能不包括其中某些类型。

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

  1. ...每个自己的财产键PO[对象被迭代]认为是一个整数指数,在上升的数字顺序索引

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

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

Map

如果您对有序地图感兴趣,则应考虑使用MapES2015中引入的类型,而不是plain Objects



7

在ES2015中,它可以,但不是您可能认为的

直到ES2015才保证对象中键的顺序。它是实现定义的。

然而,在ES2015的指定。像JavaScript中的许多事情一样,这样做是出于兼容性目的,并且通常反映了大多数JS引擎中的现有非官方标准(您知道-谁是例外)。

该顺序在规范中的抽​​象操作OrdinaryOwnPropertyKeys下定义,该操作支持对对象自己的键进行迭代的所有方法。解释如下,顺序如下:

  1. 所有整数索引键(这样的东西"1123""55"等),在上升的数字顺序。

  2. 所有不是整数索引的字符串键,按创建顺序(最早的顺序)。

  3. 所有符号键,按创建顺序(最早的顺序)。

说这个命令不可靠是很愚蠢的-它是可靠的,可能不是您想要的,现代浏览器正确地实现了这个命令。

某些例外情况包括枚举继承的键的方法,例如for .. in循环。将for .. in根据规范循环不保证秩序。


7

从ES2015开始,某些迭代属性的方法将保证属性顺序。但是没有其他人。不幸的是,不能保证有顺序的方法通常是最常用的:

  • Object.keysObject.valuesObject.entries
  • for..in 循环
  • JSON.stringify

但是,正如ES2020的,这些以前不值得信任的方法财产秩序被规范保证在同一个确定性的方式将他人进行遍历,由于对成品建议:换力学

就像具有保证迭代顺序的方法(如Reflect.ownKeysObject.getOwnPropertyNames)一样,先前未指定的方法也将按以下顺序进行迭代:

  • 数字数组键,按升序排列
  • 所有其他非符号键,按插入顺序
  • 符号键,按插入顺序

这几乎是每个实现都已经做过的(并且已经做了很多年),但是新提议使它成为正式的。

尽管当前规范以迭代顺序“ 几乎完全未指定,但实际引擎趋向于更加一致:”

ECMA-262中缺乏特异性并不能反映现实。在过去的讨论中,实现者观察到for-for的行为存在一些约束,任何想要在网络上运行代码的人都必须遵循。

因为每个实现都已经可以预测地遍历属性,所以可以将其放入规范中而不会破坏向后兼容性。


当前有几种奇怪的情况,实现方式尚未达成共识,在这种情况下,结果顺序将继续不确定。为了保证财产顺序:

被迭代的对象或其原​​型链中的任何内容都不是代理,类型化数组,模块名称空间对象或宿主奇异对象。

在迭代过程中,对象及其原型链中的任何内容都没有原型更改。

在迭代过程中,对象及其原型链中的任何内容均未删除任何属性。

对象的原型链中没有任何内容在迭代过程中添加。

在迭代过程中,对象或其原​​型链中的任何属性都没有可枚举性更改。

没有任何不可枚举的属性会掩盖不可枚举的属性。


5

正如其他人所述,当您迭代对象的属性时,您无法保证顺序。如果您需要多个字段的有序列表,我建议创建一个对象数组。

var myarr = [{somfield1: 'x', somefield2: 'y'},
{somfield1: 'a', somefield2: 'b'},
{somfield1: 'i', somefield2: 'j'}];

这样,您可以使用常规的for循环并具有插入顺序。然后,您可以根据需要使用Array sort方法将其排序到一个新数组中。


3

刚刚发现这很难。

使用React with Redux,每次更改存储时都会刷新我想遍历的状态容器的键以生成子项(根据Redux的不变性概念)。

因此,为了使用Object.keys(valueFromStore)I Object.keys(valueFromStore).sort(),我至少现在对这些键具有字母顺序。


-7

根据JSON标准

对象是零个或多个名称/值对的无序集合,其中名称是字符串,值是字符串,数字,布尔值,null,对象或数组。

(强调我的)。

因此,不,您不能保证订单。


7
这是由ECMAScript标准指定的-不是JSON规范。
Alnitak

7
@ Alnitak,@ Iacqui:JSON仅从ECMAScript规范中获取此内容。它也为JSON指定,但这与问题无关。
圣保罗Ebermann
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.