JavaScript 是否保证对象属性的顺序?
- 2024-11-02 21:00:00
- admin 原创
- 40
问题描述:
如果我创建一个这样的对象:
var obj = {};
obj.prop1 = "Foo";
obj.prop2 = "Bar";
最终的物体看起来总是这个样子吗?
{ prop1 : "Foo", prop2 : "Bar" }
也就是说,这些属性的顺序是否与我添加它们的顺序相同?
解决方案 1:
自 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:
是的(但不总是插入顺序)。
大多数浏览器以如下方式迭代对象属性:
按升序排列的正整数键(以及解析为整数的字符串,如“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 正式将事实上的标准行为纳入规范的下一个版本中。
解决方案 3:
普通对象中的属性顺序是 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 中,只有某些方法遵循以下顺序:
对象分配
对象定义属性
对象.getOwnPropertyNames
对象.getOwnPropertySymbols
Reflect.ownKeys
JSON.解析
JSON.stringify
从 ES2020 开始,其他所有规范都支持该规范(一些规范在 ES2015 和 ES2020 之间,其他规范在 ES2020 中),其中包括:
对象.keys、对象.entries、对象.values、...
对于...在
最难确定的是for-in
,它独特地包含了继承的属性。ES2020中已经实现了这一点(除了极端情况外)。以下来自链接(现已完成)提案的列表提供了未指定顺序的极端情况:
被迭代的对象及其原型链中的任何内容都不是代理、类型数组、模块命名空间对象或主机外来对象。
在迭代过程中,对象及其原型链中的任何内容的原型都不会发生变化。
该对象或其原型链中的任何内容均不具有在迭代期间被删除的属性。
对象原型链中没有任何内容具有在迭代过程中添加的属性。
对象的任何属性或其原型链中的任何内容的可枚举性在迭代期间都不会发生变化。
任何不可枚举的属性都不会掩盖可枚举的属性。
结论:即使在 ES2015 中,您也不应该依赖 JavaScript 中普通对象的属性顺序。这样很容易出错。如果您需要有序的命名对,请使用Map
纯粹使用插入顺序的。如果您只需要顺序,请使用数组或Set
(也使用纯粹的插入顺序)。
解决方案 4:
在撰写本文时,大多数浏览器确实按照插入的顺序返回属性,但这显然不能保证行为,因此不应依赖它。
ECMAScript 规范曾经说过:
未指定枚举属性的机制和顺序...。
但是在 ES2015 及更高版本中,非整数键将按插入顺序返回。
解决方案 5:
整个答案是在符合规范的背景下,而不是任何引擎在特定时刻或历史上所做的事情。
一般情况下,不
实际问题非常模糊。
这些属性的顺序是否与我添加的顺序相同
在什么情况下?
答案是:这取决于许多因素。一般来说,不会。
有时是的
您可以在此处依赖 plain 的属性键顺序Objects
:
符合 ES2015 标准的引擎
拥有房产
Object.getOwnPropertyNames()
,,Reflect.ownKeys()
Object.getOwnPropertySymbols(O)
在所有情况下,这些方法都包含不可枚举的属性键和顺序键,如[[OwnPropertyKeys]]
(见下文)所示。它们在所包含的键值类型上有所不同(String
和/或Symbol
)。在此上下文中String
包括整数值。
Object.getOwnPropertyNames(O)
返回O
自己的String
键控属性(属性名称)。
Reflect.ownKeys(O)
返回O
自己的String
- 和Symbol
-keyed 属性。
Object.getOwnPropertySymbols(O)
返回O
自己的Symbol
键控属性。
[[OwnPropertyKeys]]
顺序本质上是:整数Strings
按升序排列,非整数Strings
按创建顺序排列,符号按创建顺序排列。根据调用此函数的函数,可能不包含其中一些类型。
具体语言是按以下顺序返回键:
... [被迭代的对象] 每个自己的属性键
P
都是O
一个整数索引,按升序数字索引顺序排列... 每个属性
P
的键O
都是字符串,但不是整数索引,按属性创建顺序... 每个自己的属性键
P
都是O
一个符号,按照属性创建顺序
Map
如果您对有序地图感兴趣,您应该考虑使用Map
ES2015 中引入的类型而不是普通的类型Objects
。
解决方案 6:
从 ES2015 开始,某些迭代属性的方法可以保证属性的顺序,但其他方法则不然。不幸的是,不保证顺序的方法通常是最常用的:
Object.keys
,,Object.values
Object.entries
for..in
循环JSON.stringify
但是,从 ES2020 开始,由于已完成的提案:for-in 机制,这些先前不可信的方法的属性顺序将由规范保证以与其他方法相同的确定性方式进行迭代。
Reflect.ownKeys
就像具有保证迭代顺序的方法(如和)一样Object.getOwnPropertyNames
,先前未指定的方法也将按照以下顺序进行迭代:
数字数组键,按数字升序排列
所有其他非符号键,按插入顺序
符号键,按插入顺序
这几乎是每个实现都已经做的事情(并且已经做了很多年),但新提案已将其正式化。
尽管当前规范对迭代顺序的描述“几乎完全没有指定,但实际引擎往往更加一致:”
ECMA-262 中缺乏明确性并不反映现实。在过去几年的讨论中,实施者已经注意到,for-in 的行为存在一些限制,任何想要在 Web 上运行代码的人都需要遵守这些限制。
因为每个实现都已经可预测地迭代属性,所以它可以放入规范中而不会破坏向后兼容性。
目前,实现中还存在一些奇怪的情况,在这种情况下,结果顺序将继续不明确。为了保证属性顺序,请执行以下操作:
被迭代的对象及其原型链中的任何内容都不是代理、类型数组、模块命名空间对象或主机外来对象。
在迭代过程中,对象及其原型链中的任何内容的原型都不会发生变化。
该对象或其原型链中的任何内容均不具有在迭代期间被删除的属性。
对象原型链中没有任何内容具有在迭代过程中添加的属性。
对象的任何属性或其原型链中的任何内容的可枚举性在迭代期间都不会发生变化。
任何不可枚举的属性都不会掩盖可枚举的属性。
解决方案 7:
在现代浏览器中,您可以使用Map
数据结构而不是对象。
开发人员 mozilla > 地图
Map 对象可以按插入顺序迭代其元素......
解决方案 8:
在 ES2015 中确实如此,但可能和你想象的不一样
直到 ES2015,对象中键的顺序才得到保证。它是实现定义的。
然而,ES2015 对此进行了详细说明。与 JavaScript 中的许多事情一样,这样做是为了兼容性,并且通常反映了大多数 JS 引擎中现有的非官方标准(你知道的引擎是例外)。
该顺序在规范中定义,在抽象操作OrdinaryOwnPropertyKeys下,它支持对对象自身键进行迭代的所有方法。 解释一下,顺序如下:
所有整数索引键(诸如
"1123"
、"55"
等)按数字升序排列。所有不是整数索引的字符串键,按创建顺序排列(最旧优先)。
所有符号键,按创建顺序排列(从旧到新)。
说这个顺序不可靠是愚蠢的——它是可靠的,只是可能不是你想要的,现代浏览器正确地实现了这个顺序。
一些例外包括枚举继承键的方法,例如循环for .. in
,它不能保证按照规范的顺序。
解决方案 9:
正如其他人所说,迭代对象的属性时,无法保证顺序。如果您需要多个字段的有序列表,我建议创建一个对象数组。
var myarr = [{somfield1: 'x', somefield2: 'y'},
{somfield1: 'a', somefield2: 'b'},
{somfield1: 'i', somefield2: 'j'}];
这样,您可以使用常规 for 循环并设置插入顺序。然后,如果需要,您可以使用数组排序方法将其排序到新数组中。
解决方案 10:
对象和 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"**
解决方案 11:
刚刚才发现这是很困难的事情。
将 React 与 Redux 结合使用时,每次存储发生变化时,我想要遍历的键的状态容器都会刷新(根据 Redux 的不变性概念)。
因此,为了采取Object.keys(valueFromStore)
我使用的Object.keys(valueFromStore).sort()
,以便我现在至少有一个按字母顺序排列的按键。
解决方案 12:
对于 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;
}
}
解决方案 13:
实际上,它并不能保证顺序,但大多数情况下,它通过插入项目来排序,但在某些情况下顺序很容易改变。例如,如果你使用 Object.Keys 和 Object.Values,它们的结果在第一个顺序上是不一样的。很可能
保存顺序的一个解决方案是保存键并使用这些键按照第一个顺序访问对象。
像这样:
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;
}
解决方案 14:
实际的键顺序可能与插入顺序不同:
console.log({ 5: 5, 2: 2 })
结果是:
{ '2': 2, '5': 5 }
所以不要依赖插入顺序。
解决方案 15:
编辑:以下帖子只是开发人员工具显示问题。正如@Bergi指出的那样,迭代顺序与其他答案中描述的一样。我把它留在这里以防其他人发现同样的陷阱。为了记录,我的代码用Vite编译,"react": "^18.3.1"
我在chrome和Firefox中都测试了这一点。
我的一些依赖属性顺序的代码刚刚被破坏了。今天我进行了测试,发现它们现在的顺序如下:
数字
大写字母顺序
小写,字母顺序
因此,在我插入已排序的属性后,它们会恢复到此顺序。这适用于Object.fromEntries
带括号的插入。以下代码:
let test = {}
test["b"] = "insert order 1"
test["a"] = "insert order 2"
test[1] = "insert order 3"
test["B"] = "insert order 4"
test["A"] = "insert order 5"
test["C"] = "insert order 6"
console.log(test)
打印此内容:
解决方案 16:
来自JSON 标准:
对象是零个或多个名称/值对的无序集合,其中名称是字符串,值是字符串、数字、布尔值、空值、对象或数组。
(重点是我的)。
因此,你不能保证订单。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件