提问人:mellowsoon 提问时间:4/3/2011 最后编辑:johannchopinmellowsoon 更新时间:4/4/2023 访问量:436449
JavaScript 是否保证对象属性顺序?
Does JavaScript guarantee object property order?
问:
如果我创建这样的对象:
var obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";
生成的对象会始终如下所示吗?
{ prop1 : "Foo", prop2 : "Bar" }
也就是说,属性的顺序是否与我添加它们的顺序相同?
答:
在撰写本文时,大多数浏览器确实以与插入相同的顺序返回属性,但显然不能保证其行为,因此不应依赖该属性。
ECMAScript 规范曾经说过:
枚举属性的机制和顺序......未指定。
但是,在 ES2015 及更高版本中,非整数键将按插入顺序返回。
评论
自 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 对象
对象是 类型 Object。它是属性的无序集合,每个属性 包含基元值、对象或 功能。存储在 对象的属性称为 方法。
评论
(2**32) - 1
从 JSON 标准:
对象是零个或多个名称/值对的无序集合,其中名称是字符串,值是字符串、数字、布尔值、null、对象或数组。
(强调我的)。
所以,不,你不能保证订单。
评论
是(但并不总是广告订单)。
大多数浏览器将对象属性迭代为:
- 按升序排列的正整数键(以及解析为整数的字符串,如“1”)
- 字符串键,按插入顺序排列(ES2015 保证这一点,所有浏览器都遵守)
- 符号名称,按插入顺序排列(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 正式将事实上的标准行为纳入规范的下一个版本。
评论
createFragment
API 已经依赖于这个......🤔
正如其他人所说,当你遍历一个对象的属性时,你无法保证顺序。如果你需要一个包含多个字段的有序列表,我建议创建一个对象数组。
var myarr = [{somfield1: 'x', somefield2: 'y'},
{somfield1: 'a', somefield2: 'b'},
{somfield1: 'i', somefield2: 'j'}];
这样,您可以使用常规的 for 循环并具有插入顺序。然后,如果需要,可以使用 Array sort 方法将其排序到新数组中。
在现代浏览器中,您可以使用数据结构而不是对象。Map
Map 对象可以按插入顺序迭代其元素...
整个答案是在规范合规性的背景下,而不是任何引擎在特定时刻或历史上所做的事情。
一般是不行的
实际问题非常模糊。
属性的顺序是否与我添加的顺序相同
在什么情况下?
答案是:这取决于许多因素。一般来说,不可以。
有时,是的
在这里,您可以依靠普通属性键顺序:Objects
- ES2015兼容引擎
- 自有房产
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)
返回自己的 -keyed 属性。O
Symbol
[[OwnPropertyKeys]]
顺序本质上是:升序为整数,创建顺序为非整数,创建顺序为符号。根据调用此函数的函数,可能不包括其中一些类型。Strings
Strings
具体语言是按以下顺序返回密钥:
...[正在迭代的对象]的每个自己的属性键都是一个整数索引,按数字索引升序排列
P
O
...每个自己的属性键都是 String,但不是整数索引,按属性创建顺序排列
P
O
...每个自己的属性键都是一个 Symbol,按属性创建顺序排列
P
O
Map
如果您对有序地图感兴趣,您应该考虑使用 ES2015 中引入的类型,而不是 plain 。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"
}
- 按升序排列的正整数键
- 按广告顺序排列的字符串键
- 按广告顺序排列的符号
因此,有三个段可能会改变插入顺序(如示例中所示)。而类似正整数的键根本不会遵守广告订单。
在 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
(也使用纯插入顺序)。
只是艰难地发现了这一点。
将 React 与 Redux 一起使用,每次更改存储时,我想要遍历其键以生成子项的状态容器都会刷新(根据 Redux 的不可变性概念)。
因此,为了采取我使用的 ,因此我现在至少对键有一个字母顺序。Object.keys(valueFromStore)
Object.keys(valueFromStore).sort()
在 ES2015 中,它确实如此,但不是你想象的那样
直到 ES2015 之前,无法保证对象中键的顺序。它是实现定义的。
但是,在 ES2015 中指定了。就像 JavaScript 中的许多事情一样,这样做是为了兼容性目的,并且通常反映了大多数 JS 引擎中现有的非官方标准(你知道谁是一个例外)。
顺序在规范中定义的抽象操作 OrdinaryOwnPropertyKeys,该操作支持遍历对象自身键的所有方法。释义一下,顺序如下:
所有整数索引键(如 、 等)按数字升序排列。
"1123"
"55"
所有不是整数索引的字符串键,按创建顺序(最旧-最先)。
所有符号键,按创建顺序(从旧到先)。
说顺序不可靠是愚蠢的——它是可靠的,只是它可能不是你想要的,而现代浏览器正确地实现了这个顺序。
一些例外包括枚举继承键的方法,例如循环。该循环不保证根据规范的顺序。for .. in
for .. in
评论
{99999: "John",1: "a",age: 30,696:'12312'}
从 ES2015 开始,某些迭代属性的方法可以保证属性顺序。但不是其他人。不幸的是,不能保证有订单的方法通常是最常用的:
Object.keys
, ,Object.values
Object.entries
for..in
循环JSON.stringify
但是,从 ES2020 开始,这些以前不可信的方法的属性顺序将由规范保证,该规范将以与其他方法相同的确定性方式进行迭代,这是由于完成的提案:for-in 机制。
就像具有保证迭代顺序的方法(如 和 )一样,以前未指定的方法也将按以下顺序迭代:Reflect.ownKeys
Object.getOwnPropertyNames
- 数字数组键,按数字升序排列
- 所有其他非符号键(按广告顺序排列)
- 符号键(按广告顺序排列)
这是几乎每个实现都已经做的事情(并且已经做了很多年),但新提案已经正式化了。
虽然目前的规范留下了..在迭代顺序中,“几乎完全没有指定,真正的引擎往往更加一致:”
ECMA-262 缺乏特异性并不能反映现实。在多年前的讨论中,实现者观察到,for-in 的行为存在一些限制,任何想要在 Web 上运行代码的人都需要遵守这些约束。
由于每个实现都已可预测地迭代属性,因此可以在不破坏向后兼容性的情况下将其放入规范中。
有一些奇怪的情况,目前实现没有达成一致,在这种情况下,生成的顺序将继续未指定。为了保证财产秩序:
被迭代的对象及其原型链中的任何内容都不是代理、类型化数组、模块命名空间对象或宿主外来对象。
在迭代过程中,对象及其原型链中的任何内容都不会更改其原型。
在迭代期间,对象及其原型链中的任何内容都不会删除属性。
对象的原型链中没有任何内容在迭代期间添加了属性。
在迭代期间,对象的任何属性或其原型链中的任何内容都不会发生可枚举性更改。
任何不可枚举属性都不会隐藏可枚举属性。
对象和 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"**
评论
var
let
不是新的var
对于 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;
}
}
评论
实际上,它并不能保证顺序,但最有可能的是,它通过插入项目进行排序,但在某些情况下,顺序容易更改。例如,如果您使用 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;
}
评论
for-in
Object.keys
)for-in