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

Does JavaScript guarantee object property order?

提问人:mellowsoon 提问时间:4/3/2011 最后编辑:johannchopinmellowsoon 更新时间:4/4/2023 访问量:436449

问:

如果我创建这样的对象:

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

生成的对象会始终如下所示吗?

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

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

JavaScript 对象

评论

5赞 Bergi 12/11/2015
同样相关的是:ES6 是否为对象属性引入了定义明确的枚举顺序?
9赞 T.J. Crowder 5/21/2017
这里公认的答案不再准确。在此处查看最新答案。
1赞 zero298 11/3/2017
@T.J.Crowder:你能更详细地谈谈为什么公认的答案不再准确吗?您链接的问题似乎可以归结为属性顺序仍然无法保证每个规范的想法。
3赞 T.J. Crowder 11/3/2017
@zero298:该问题的公认答案清楚地描述了截至 ES2015+ 的指定属性顺序。遗留操作 (, ) 不必(正式)支持它,但现在秩序。(非正式地:Firefox、Chrome 和 Edge 都遵循指定的顺序,即使在 for-in 和 Object.keys 中也是如此,在这些情况下,它们没有被正式要求: jsfiddle.net/arhbn3k2/1for-inObject.keys)
2赞 T.J. Crowder 4/15/2021
现在,从ES2020开始,甚至有一个明确的顺序;我已经用当前的游戏状态更新了这个答案for-in

答:

80赞 Alnitak 4/3/2011 #1

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

ECMAScript 规范曾经说过:

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

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

评论

17赞 Tim Down 4/3/2011
Chrome 实现的顺序与其他浏览器不同。查看 code.google.com/p/v8/issues/detail?id=164
9赞 gsnedders 4/3/2011
Opera 10.50 及更高版本以及 IE9 符合 Chrome 的顺序。Firefox 和 Safari 现在是少数(它们也对对象/数组使用不同的顺序)。
1赞 Alnitak 3/10/2015
@Veverke明确没有订单保证,因此应该始终假设订单实际上是随机的。
1赞 Alnitak 3/10/2015
@Veverke 不,顺序完全不是那么可预测的。它取决于实现,并且随时可能发生变化,并且每次浏览器自我更新时都可能发生变化(例如)。
4赞 Benjamin Gruenbaum 10/1/2015
这个答案在 ES2015 中是错误的。
732赞 bpierre 4/3/2011 #2

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

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

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

Map 中的键是有序的,而添加到对象的键则没有。因此,在循环访问它时,Map 对象会按插入顺序返回键。(请注意,在 ECMAScript 2015 规范中,对象确实保留了字符串和符号键的创建顺序,因此仅使用字符串键遍历对象将按插入顺序生成键)

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

4.3.3 对象

对象是 类型 Object。它是属性的无序集合,每个属性 包含基元值、对象或 功能。存储在 对象的属性称为 方法。

评论

7赞 Dave Dopson 12/29/2019
整数键的行为并非在所有浏览器中都一致。一些较旧的浏览器按插入顺序(使用字符串键)迭代整数键,而有些浏览器则按升序迭代整数键。
5赞 T.J. Crowder 1/2/2020
@DaveDopson - 对 - 过时的浏览器不遵循当前的规范,因为它们没有更新。
1赞 cementblocks 12/7/2021
“整数键”是否正确?根据我的有限测试,只有整数键/非负整数键按升序排序。
1赞 Nick Parsons 10/13/2023
@cementblocks“整数键”有点宽泛。它实际上是一个对象的“数字数组键”,基本上是介于 0(含)和 (2^32) - 1(不含)之间的键,将按插入顺序迭代。因此,负数、非整数和等于或更大的数字将按插入顺序迭代,类似于字符串键:)(2**32) - 1
-7赞 Kevin Lacquement 4/3/2011 #3

JSON 标准

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

(强调我的)。

所以,不,你不能保证订单。

评论

8赞 Alnitak 4/3/2011
这是由 ECMAScript 标准指定的,而不是 JSON 规范。
7赞 Paŭlo Ebermann 7/2/2011
@Alnitak,@Iacqui:JSON 仅从 ECMAScript 规范中获取这一点。它也是为 JSON 指定的,但这与问题无关。
319赞 Dave Dopson 4/22/2014 #4

是(但并不总是广告订单)。

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

  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赞 Dave Dopson 10/1/2015
@BenjaminGruenbaum - 这正是我的观点。截至 2014 年,所有主要供应商都有一个共同的实现,因此标准最终将遵循(即 2015 年)。
5赞 mik01aj 7/4/2016
顺便说一句:React createFragment API 已经依赖于这个......🤔
9赞 Piotr Dobrogost 10/7/2016
@BenjaminGruenbaum 您的评论是错误的。在 ES2015 中,仅保证所选方法的顺序。请参阅下面的 ftor 的答案
0赞 Jose V 1/12/2021
@mik01aj想链接 React 的源代码吗?(是的,我很懒)
0赞 Artur Carvalho 5/10/2021
@DaveDopson第 1 点。它只是非负整数键,对吗?
9赞 Drew Durham 1/28/2015 #5

正如其他人所说,当你遍历一个对象的属性时,你无法保证顺序。如果你需要一个包含多个字段的有序列表,我建议创建一个对象数组。

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

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

17赞 Topicus 6/2/2015 #6

在现代浏览器中,您可以使用数据结构而不是对象。Map

开发商: mozilla > Map

Map 对象可以按插入顺序迭代其元素...

47赞 JMM 8/22/2015 #7

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

一般是不行的

实际问题非常模糊。

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

在什么情况下?

答案是:这取决于许多因素。一般来说,不可以

有时,是的

在这里,您可以依靠普通属性键顺序:Objects

  • ES2015兼容引擎
  • 自有房产
  • Object.getOwnPropertyNames(), ,Reflect.ownKeys()Object.getOwnPropertySymbols(O)

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

Object.getOwnPropertyNames(O)

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

Reflect.ownKeys(O)

返回自己的 - 和 -键控属性。OStringSymbol

Object.getOwnPropertySymbols(O)

返回自己的 -keyed 属性。OSymbol

[[OwnPropertyKeys]]

顺序本质上是:升序为整数,创建顺序为非整数,创建顺序为符号。根据调用此函数的函数,可能不包括其中一些类型。StringsStrings

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

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

  2. ...每个自己的属性键都是 String,但不是整数索引,按属性创建顺序排列PO

  3. ...每个自己的属性键都是一个 Symbol,按属性创建顺序排列PO

Map

如果您对有序地图感兴趣,您应该考虑使用 ES2015 中引入的类型,而不是 plain 。MapObjects

154赞 user6445533 7/6/2016 #8

普通对象中的属性顺序在 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.getOwnProperty名称
  • Object.getOwnPropertySymbols
  • 反射.ownKeys
  • JSON.解析
  • JSON.stringify(JSON.字符串化)

截至 ES2020,所有其他产品都这样做(有些在 ES2015 和 ES2020 之间的规格中,有些在 ES2020 中),其中包括:

  • Object.keys、Object.entries、Object.values、...
  • 为。。在

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

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

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

5赞 kriskate 2/2/2019 #9

只是艰难地发现了这一点。

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

因此,为了采取我使用的 ,因此我现在至少对键有一个字母顺序。Object.keys(valueFromStore)Object.keys(valueFromStore).sort()

12赞 GregRos 2/13/2019 #10

在 ES2015 中,它确实如此,但不是你想象的那样

直到 ES2015 之前,无法保证对象中键的顺序。它是实现定义的。

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

顺序在规范中定义的抽象操作 OrdinaryOwnPropertyKeys,该操作支持遍历对象自身键的所有方法。释义一下,顺序如下:

  1. 所有整数索引键(如 、 等)按数字升序排列。"1123""55"

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

  3. 所有符号键,按创建顺序(从旧到先)。

说顺序不可靠是愚蠢的——它是可靠的,只是它可能不是你想要的,而现代浏览器正确地实现了这个顺序。

一些例外包括枚举继承键的方法,例如循环。该循环不保证根据规范的顺序。for .. infor .. in

评论

0赞 Rman__ 8/27/2023
实际上我测试了它,是的,这就是 js 处理数组顺序的方式,它在数组顶部发送数字,例如,顺序最终会像 { 1: “a”, 696: “12312”, 99999: “John”, age: 30 } 所以请注意你的数字索引{99999: "John",1: "a",age: 30,696:'12312'}
26赞 CertainPerformance 10/18/2019 #11

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

  • Object.keys, ,Object.valuesObject.entries
  • for..in循环
  • JSON.stringify

但是,从 ES2020 开始,这些以前不可信的方法的属性顺序由规范保证,该规范将以与其他方法相同的确定性方式进行迭代,这是由于完成的提案:for-in 机制

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

  • 数字数组键,按数字升序排列
  • 所有其他非符号键(按广告顺序排列)
  • 符号键(按广告顺序排列)

这是几乎每个实现都已经做的事情(并且已经做了很多年),但新提案已经正式化了。

虽然目前的规范留下了..在迭代顺序中,“几乎完全没有指定,真正的引擎往往更加一致:”

ECMA-262 缺乏特异性并不能反映现实。在多年前的讨论中,实现者观察到,for-in 的行为存在一些限制,任何想要在 Web 上运行代码的人都需要遵守这些约束。

由于每个实现都已可预测地迭代属性,因此可以在不破坏向后兼容性的情况下将其放入规范中。


有一些奇怪的情况,目前实现没有达成一致,在这种情况下,生成的顺序将继续未指定。为了保证财产秩序:

被迭代的对象及其原型链中的任何内容都不是代理、类型化数组、模块命名空间对象或宿主外来对象。

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

在迭代期间,对象及其原型链中的任何内容都不会删除属性。

对象的原型链中没有任何内容在迭代期间添加了属性。

在迭代期间,对象的任何属性或其原型链中的任何内容都不会发生可枚举性更改。

任何不可枚举属性都不会隐藏可枚举属性。

6赞 Ajay Gangarde 6/23/2020 #12

对象和 MAP 之间的主要区别示例:

它是循环迭代的顺序,在 Map 中,它遵循创建时设置的顺序,而在 OBJECT 中则不然。

请参见: 对象

const obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";
obj['1'] = "day";
console.log(obj)

**OUTPUT: {1: "day", prop1: "Foo", prop2: "Bar"}**

地图

    const myMap = new Map()
    // setting the values
    myMap.set("foo", "value associated with 'a string'")
    myMap.set("Bar", 'value associated with keyObj')
    myMap.set("1", 'value associated with keyFunc')

OUTPUT:
**1.    ▶0: Array[2]
1.   0: "foo"
2.   1: "value associated with 'a string'"
2.  ▶1: Array[2]
1.   0: "Bar"
2.   1: "value associated with keyObj"
3.  ▶2: Array[2]
1.   0: "1"
2.   1: "value associated with keyFunc"**

评论

6赞 vdegenne 11/26/2020
2020 年谁还在使用?var
1赞 Alireza 9/20/2021
let不是新的var
2赞 keth-tex 3/14/2022 #13

对于 100% 故障安全解决方案,您可以使用嵌套对象并执行如下操作:

const obj = {};
obj.prop1 = {content: "Foo", index: 0};
obj.prop2 = {content: "Bar", index: 1};

for (let i = 0; i < Object.keys(obj).length; i++)
for (const prop in obj) {
    if (obj[prop].index == i) {
        console.log(obj[prop].content);
        break;
    }
}

评论

3赞 Mark Fisher 6/14/2022
这是 O(n^2),可能不是一个很好的解决方案。
0赞 keth-tex 6/25/2022
@MarkFisher我想这是故障安全的权衡。但是,这里仍然可以进行优化:我添加了一个“中断”。现在大约是 (n^2)/2。
1赞 Mark Fisher 6/25/2022
我认为没有必要在性能上进行权衡。例如,德鲁·达勒姆(Drew Durham)的解决方案如何,它可以让你在O(n)中做同样的事情,而不是故障保护?此外,O(n^2 / 2) 仍然是 O(n^2)。
1赞 Amir Kian 4/4/2023 #14

实际上,它并不能保证顺序,但最有可能的是,它通过插入项目进行排序,但在某些情况下,顺序容易更改。例如,如果您使用 Object.键和对象。值,它们的结果在第一顺序上是不一样的。可能

保存顺序的解决方案是保存键,并使用这些键以第一个顺序访问对象。

像这样的东西:

MergeFlatedObjValue(obj){
  const myJSONString = JSON.stringify(obj);
  const regexp = /(?<!:)("[\w.]+")/g;
let matches = myJSONString.matchAll(regexp);
var MergeFlatedObjValue="";   
for (const match of matches) {
  var matched=match[0];
   matched=matched.replace(/['"]+/g, '');
  MergeFlatedObjValue+=obj[matched]+"#";

}
  return MergeFlatedObjValue;

  }