如何按照书写顺序迭代JavaScript对象属性

20

我发现了我的代码中的一个错误,希望能够通过最小化重构工作来解决。这个错误发生在Chrome和Opera浏览器中。

var obj = {23:"AA",12:"BB"};
//iterating through obj's properties
for(i in obj)
  document.write("Key: "+i +" "+"Value: "+obj[i]);

在FF和IE中输出
键:23 值:AA
键:12 值:BB

在Opera和Chrome中输出(错误)
键:12 值:BB
键:23 值:AA

我尝试创建一个反向排序的对象,如下所示

var obj1={"AA":23,"BB":12};
for(i in obj1)
  document.write("Key: "+obj[i] +" "+"Value: "+i);

但是输出结果都相同。有没有办法在做出小改动的情况下,让所有浏览器都表现出相同的行为?


你不应该给属性命名违反标准标识符规则的名称(例如,以数字开头的名称)。 - 3Dave
1
其实这没问题:ObjectLiteral 中的 PropertyName 可以是 NumericLiteral。请参阅 ECMA262-5 第 11.1.5 节。个人而言,我更喜欢将其引用为 '23',以明确对象属性始终为字符串,但两种方式都是有效的。 - bobince
这里有一些好的答案:https://dev59.com/jW035IYBdhLWcg3wQtsg - Déjà vu
5个回答

22

JavaScript对象属性没有固定的顺序。使用for...in循环时,属性的顺序是随机的。

如果你需要有序,你需要使用数组:

var map= [[23, 'AA'], [12, 'BB']];
for (var i= 0; i<map.length; i++)
    document.write('Key '+map[i][0]+', value: '+map[i][1]);

具体来说,这是因为JavaScript对象是哈希表(想想哈希表)。如果您想要排序,请使用数组。 - Matt Ball
3
当程序员缺乏深刻的理解却写代码时,就会出现这种情况。 :) - Eugeniu Torica
讨厌的程序员!我讨厌他们! :-) - bobince

2

我认为你会发现,唯一可靠的方法是使用数组而不是关联数组,例如:

var arr = [{key:23,val:"AA"},{key:12,val:"BB"}];
for(var i=0; i<arr.length; i++)
  document.write("Key: "+arr[i].key +" "+"Value: "+arr[i].val);

JavaScript具有带有属性的对象,而不是关联数组。如果它们是数组,它们将是有序的。 - Álvaro González
同意 - 但是具有属性的对象通常(我承认并非完全正确)被称为“关联数组”,或者至少被用于此目的。 “关联数组”(在更一般的、不是JavaScript意义上)通常不被认为等同于有序值 - 它们被认为是名称/值对的集合。 - Graza

2
根据ES2015规范,数字键按数字顺序迭代,非数字键按插入顺序迭代。这是规范保证的,因此您可以依赖它。
如果您想始终使用插入顺序,即使对于数字键,那么您必须添加前缀(例如_)使它们成为非数字键:
var obj = {_23:"AA",_12:"BB"};
//iterating through obj's properties
for(i in obj)
  document.write("Key: "+i +" "+"Value: "+obj[i]);

1

@bobince是正确的,对象不保留任何排序元数据。

在我的情况下,重构为数组没有意义,因此我提供另一种解决方案:创建一个具有您所需顺序的数组,并使用它按顺序映射对象属性:

const obj = {
    'r': '#f00',
    'g': '#0f0',
    'b': '#00f',
};
const objMap = ['b','r','g'];

objMap.map((key, index) => {
    console.log(`array index: ${index}`);
    console.log(`object index: ${key}`);
    console.log(`object property: ${obj[key]}\n`);
});

输出:

array index: 0
object index: b
object property: #00f

array index: 1
object index: r
object property: #f00

array index: 2
object index: g
object property: #0f0

-1
当对象属性标识符为字母字符串时,我无法获得您的结果。IE8、FF5、Chrome 12 和 Opera 9.8 都保留了创建顺序,即:

Key: AA Value: 23 Key: BB Value: 12

只有当标识符是数字时,结果才与您的匹配:

IE8、FF5 --> Key: 23 Value: AA Key: 12 Value: BB

Opera、Chrome --> Key: 12 Value: BB Key: 23 Value: AA

因为 12 小于 23,所以 Opera 和 Chrome 是按相反的创建顺序存储的,如果您改用:

var obj = {2:"AA",12:"BB"};

然后你会在所有的4个浏览器中得到以下结果:

键:2 值:AA 键:12 值:BB

因此,使用数字作为标识符会导致差异。如果标识符是字母表中的,则在4个浏览器中创建和存储属性的顺序相同。这是尽管ECMA规则指出存储顺序不必与创建顺序相关。

如果字符串标识符是数字,例如'23'和'12',那么Opera和Chrome将其视为数字,并再次反转创建顺序,从而不允许类型。'23a'类型是可以的,'a23'类型也是可以的。

回答你的问题,使用非数字的字符串标识符,行为在4个浏览器(可能所有浏览器)中都是相同的。


5
ECMAScript 规范指出,顺序是未定义的,这并不意味着它不应该有顺序。它的意思是,如果浏览器决定更改其底层数据结构,并且恰好以不同的顺序将键返回给您,则根据规范,这完全可以接受。这就是为什么即使现在运行良好,您也不应该依赖它。您还需要记住,实现可能会从一个平台更改到另一个平台,并且每个浏览器版本中的实现也可能会发生变化。 - HoLyVieR
@HoLyVieR,ES2015规范现在保证非数字键总是按插入顺序迭代。数字键按数字顺序迭代。 - Pauan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接