如果我创建一个这样的对象:
var obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";
生成的对象总是这样吗?
{ prop1 : "Foo", prop2 : "Bar" }
也就是说,属性是否与我添加它们的顺序相同?
如果我创建一个这样的对象:
var obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";
生成的对象总是这样吗?
{ prop1 : "Foo", prop2 : "Bar" }
也就是说,属性是否与我添加它们的顺序相同?
自 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 对象
对象是对象类型的成员。它是一个无序的属性集合,每个属性都包含一个原始值、对象或函数。存储在对象属性中的函数称为方法。
是(对于非整数键)。
大多数浏览器将对象属性迭代为:
一些较旧的浏览器结合了类别 #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 将事实上的标准行为正式合并到规范的下一个版本中。
普通对象中的属性顺序是 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"
}
“拥有”(非继承)属性的顺序是:
因此,有三个段,它们可能会改变插入顺序(如示例中发生的那样)。并且类似整数的键根本不遵守插入顺序。
在 ES2015 中,只有某些方法遵循以下顺序:
从 ES2020 开始,所有其他人都这样做(一些在 ES2015 和 ES2020 之间的规范中,其他在 ES2020 中),其中包括:
最难确定的是for-in
,它独特地包括继承的属性。这是在 ES2020 中完成的(除了边缘情况外)。链接(现已完成)提案中的以下列表提供了未指定顺序的边缘情况:
结论:即使在 ES2015 中,您也不应该依赖 JavaScript 中普通对象的属性顺序。它很容易出错。如果您需要有序命名对,请Map
改用,它纯粹使用插入顺序。如果您只需要订单,请使用数组或Set
(也使用纯插入顺序)。
在撰写本文时,大多数浏览器确实以与插入相同的顺序返回属性,但它明确不能保证行为,因此不应该依赖。
在ECMAScript规范常说的:
枚举属性的机制和顺序......没有指定。
但是在 ES2015 和更高版本中,非整数键将按插入顺序返回。
这整个答案是在规范合规性的背景下,而不是任何引擎在特定时刻或历史上所做的。
实际问题非常模糊。
属性是否与我添加它们的顺序相同
在什么情况下?
答案是:这取决于许多因素。一般来说,没有。
在这里,您可以指望 plain 的属性键顺序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
,创建顺序为非整数,创建顺序为符号。根据哪个函数调用它,其中一些类型可能不包括在内。
具体语言是key按以下顺序返回:
... [正在迭代的对象] 的每个自己的属性键
P
,O
它是一个整数索引,按数字索引升序排列......每一个自己的财产关键
P
的O
是一个字符串,但不是整数指数,物业创建顺序......每一个自己的财产关键
P
的O
是一个符号,在属性创建顺序
Map
如果您对有序映射感兴趣,您应该考虑使用Map
ES2015 中引入的类型而不是普通的Objects
。