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

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

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

生成的对象总是看起来像这样吗?

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

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

381035 次浏览

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

ECMAScript规范曾经说过:

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

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

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

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

使用数组或#0对象可以更好地实现这一点。MapObject保证密钥按插入顺序迭代有一些相似之处,无一例外:

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

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

4.3.3对象

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

JSON标准

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

(强调我的)。

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

是(但不总是插入顺序)。

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

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

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

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

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

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

标准总是遵循实现,这就是XHR的由来,Google通过实现Gears然后拥抱等效的HTML5功能来做同样的事情。正确的解决方案是让ECMA正式将事实上的标准行为纳入规范的下一个版本。

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

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

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

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

开发者mozilla>地图

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

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

一般来说,没有

实际问题非常模糊。

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

在什么背景下?

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

有时候是的

这里是您可以指望普通Objects的属性键顺序的地方:

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

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

Object.getOwnPropertyNames(O)

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

Reflect.ownKeys(O)

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

Object.getOwnPropertySymbols(O)

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

[[OwnPropertyKeys]]

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

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

  1. …每个属性键P ofO[被迭代的对象]是一个整数索引,按升序数字索引顺序

  2. …每个属性键P ofO是String但不是整数索引,按属性创建顺序

  3. …每个自己的属性键PO是一个符号,按属性创建顺序

Map

如果你对有序地图感兴趣,你应该考虑使用ES2015中引入的Map类型而不是普通的Objects

普通对象中的属性顺序在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.assign
  • Object.define属性
  • Object.get所有权
  • Object.get属性
  • Reflect.own钥匙
  • JSON.parse
  • JSON.stringify

截至ES2020,所有其他的都有(一些在ES2015和ES2020之间,其他在ES2020之间),其中包括:

  • Object.keysObject.entriesObject.values…
  • 为…在

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

  • 正在迭代的对象及其原型链中的任何内容都不是代理、类型化数组、模块命名空间对象或主机异国对象。
  • 在迭代期间,对象或其原型链中的任何东西都不会改变其原型。
  • 对象及其原型链中的任何内容都没有在迭代期间删除的属性。
  • 对象的原型链中没有任何内容在迭代期间添加属性。
  • 对象或其原型链中的任何属性在迭代期间都不会改变其可枚举性。
  • 不可枚举属性不会隐藏可枚举属性。

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

只是发现了这个艰难的方式。

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

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

在ES2015中,它确实如此,但不是你可能会想到的

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

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

顺序在规范中定义,在抽象操作普通自有财产密钥下,它支持迭代对象自己的键的所有方法。解释,顺序如下:

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

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

  3. 所有符号键,按创建顺序(最老优先)。

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

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

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

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

由于已完结提案,但是,从ES2020开始,这些以前不可信的方法的属性顺序将由规范保证将以与其他相同的确定性方式迭代:进给力学

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

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

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

尽管当前规范以“几乎完全未指明”的迭代顺序为…,但真正的引擎往往更一致:

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

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


目前有一些奇怪的情况实现没有同意,在这种情况下,结果顺序将继续未指定。对于属性顺序得到保障

正在迭代的对象及其原型链中的任何内容都不是代理、类型化数组、模块命名空间对象或主机异国对象。

在迭代期间,对象或其原型链中的任何东西都不会改变其原型。

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

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

对象或其原型链中的任何属性在迭代期间都不会改变其可枚举性。

不可枚举属性不会隐藏可枚举属性。

Object和MAP的主要区别:

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

请看:物体

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

MAP

    const myMap = new Map()// setting the valuesmyMap.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"**

对于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;}}