如果我创建这样的对象:
var obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";
生成的对象总是会是这样的吗?
{ prop1 : "Foo", prop2 : "Bar" }
也就是说,属性是否与我添加它们的顺序相同?
答案 0 :(得分:400)
不,JavaScript中不保证对象中的属性顺序;你需要使用Array
。
来自ECMAScript Third Edition (pdf)的对象的定义:
4.3.3对象
一个对象是一个成员 类型对象。 这是一个无序的属性集合 包含原始值,对象或 功能。存储在a中的函数 对象的属性称为a 方法
自ECMAScript 2015 以来,使用the Map
object可能是另一种选择。 Map
与Object
和guarantees the keys order分享一些相似之处:
Map以插入顺序迭代其元素,而没有为Objects指定迭代顺序。
答案 1 :(得分:148)
当前语言规范(自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 :(得分:66)
在撰写本文时,大多数浏览器确实以与插入时相同的顺序返回属性,但显然不能保证行为,因此不应该依赖它。
未指定枚举属性的机制和顺序。
但是在ES2015及更高版本中,非整数键将按插入顺序返回。
答案 3 :(得分:66)
普通对象中的属性顺序是Javascript中的复杂主题。
虽然在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"
}
因此,有三个段可能会改变插入顺序(如示例中所示)。类似整数的键根本不会影响插入顺序。
问题是,在ES2015规范中,该订单的保证方法是什么?
以下方法保证显示的顺序:
以下方法/循环保证完全没有订单:
结论:即使在ES2015中,您也不应该依赖Javascript中普通对象的属性顺序。它容易出错。请改用Map
。
答案 4 :(得分:42)
这整个答案都是在规范合规的背景下,而不是任何引擎在特定时刻或历史上所做的事情。
实际问题非常模糊。
属性的顺序与我添加的顺序相同
在什么情况下?
答案是:它取决于许多因素。通常,否。
在这里您可以依赖普通Objects
的属性键顺序:
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
按创建顺序排列,符号按创建顺序排列。根据哪个函数调用此函数,可能不包括其中一些类型。
特定语言是按以下顺序返回键:
...
P
[正在迭代的对象]的每个自有属性键O
,它是一个整数索引,按升序数字索引顺序...
P
的每个属性键O
,它是一个字符串但不是整数索引,属性创建顺序- 醇>
...属性创建顺序为
P
的{{1}}的每个属性键O
Map
如果您对有序地图感兴趣,则应考虑使用ES2015中引入的Map
类型,而不是普通Objects
。
答案 5 :(得分:12)
答案 6 :(得分:5)
正如其他人所说,当您迭代对象的属性时,您无法保证订单的顺序。如果您需要多个字段的有序列表,我建议创建一个对象数组。
var myarr = [{somfield1: 'x', somefield2: 'y'},
{somfield1: 'a', somefield2: 'b'},
{somfield1: 'i', somefield2: 'j'}];
这样您就可以使用常规for循环并具有插入顺序。然后,您可以根据需要使用Array排序方法将其排序为新数组。
答案 7 :(得分:4)
直到ES2015才保证对象中键的顺序。它是实现定义的。
但是,在ES2015中指定了 。像JavaScript中的许多事情一样,这样做是出于兼容性目的,通常反映了大多数JS引擎中的现有非官方标准(您知道-谁是例外)。
该顺序在规范中的抽象操作OrdinaryOwnPropertyKeys下定义,该操作支持对对象自己的键进行迭代的所有方法。解释如下,顺序如下:
所有整数索引键(诸如"1123"
,"55"
之类的东西)都按数字升序排列。
所有不是整数索引的字符串键,按创建顺序(最早的顺序)。
所有符号键,按创建顺序(最早的顺序)。
说顺序不可靠是很愚蠢的-它是可靠的,可能不是您想要的,现代浏览器正确地实现了该顺序。
某些异常包括枚举继承键的方法,例如for .. in
循环。 for .. in
循环不保证根据规范的顺序。
答案 8 :(得分:2)
只是很难找到答案。
使用带有Redux的React,每次更改存储时都会刷新我想要遍历的状态容器的键以生成子项(根据Redux的不变性概念)。
因此,为了使用Object.keys(valueFromStore)
,我使用了Object.keys(valueFromStore).sort()
,以便至少现在对这些键按字母顺序排序。
答案 9 :(得分:1)
从ES2015开始,某些迭代属性的方法保证了属性顺序。 but not others。不幸的是,不能保证有顺序的方法通常是最常用的:
Object.keys
,Object.values
,Object.entries
for..in
循环JSON.stringify
但是很快(可能在ES2020中),规范将保证这些以前不可信任的方法 的属性顺序以与确定方法相同的确定性方式进行迭代。其他由于stage 4提案:For-in enumeration order。
就像具有保证迭代顺序的方法(例如Reflect.ownKeys
和Object.getOwnPropertyNames
)一样,以前未指定的方法也将按照以下顺序进行迭代:
这几乎是每个实现都要做的,但是新提案将使其正式化。
尽管当前规范以。almost totally unspecified的迭代顺序保留,但实际引擎趋向于更加一致:”
ECMA-262中缺乏特异性并不能反映现实。在过去的讨论中,实现者观察到for-for的行为存在一些约束,任何想要在网络上运行代码的人都必须遵循。
由于每个实现都已经可以预测地遍历属性,因此可以将其放入规范中而不会破坏向后兼容性。
有几种奇怪的情况,当前实现 不能达成共识,在这种情况下,结果顺序将继续不确定。对于财产订单to be guaranteed:
被迭代的对象或其原型链中的任何内容都不是代理,类型化数组,模块名称空间对象或宿主奇异对象。
在迭代过程中,对象及其原型链中的任何东西都没有原型更改。
对象及其原型链中的任何内容都没有在迭代过程中删除的属性。
对象的原型链中没有任何内容在迭代过程中添加。
在迭代过程中,对象或其原型链中的任何属性都没有可枚举性更改。
没有不可枚举的属性会遮盖可枚举的属性。
答案 10 :(得分:0)
对象与MAP之间的主要差异,例如:
这是循环中的迭代顺序,在Map中,它遵循创建时设置的顺序,而在OBJECT中则不是。
查看: 对象
var obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";
obj['1'] = "day";
console.log(obj)
**OUTPUT: {1: "day", prop1: "Foo", prop2: "Bar"}**
地图
let 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"**
答案 11 :(得分:-5)