【问题标题】:Elements order in a "for (… in …)" loop“for (... in ...)”循环中的元素顺序
【发布时间】:2010-09-21 19:07:24
【问题描述】:

Javascript 中的“for...in”循环是否按照声明的顺序遍历哈希表/元素?是否有浏览器不按顺序执行?
我希望使用的对象将被声明一次并且永远不会被修改。

假设我有:

var myObject = { A: "Hello", B: "World" };

我进一步使用它们:

for (var item in myObject) alert(item + " : " + myObject[item]);

在大多数体面的浏览器中,我可以期望 'A : "Hello"' 总是出现在 'B : "World"' 之前吗?

【问题讨论】:

标签: javascript for-loop


【解决方案1】:

Quoting John Resig:

目前所有主流浏览器都按顺序循环遍历对象的属性 他们被定义了。 Chrome 也这样做,除了几个案例。 [...] ECMAScript 规范明确未定义此行为。 在 ECMA-262 中,第 12.6.4 节:

枚举属性的机制...取决于实现。

但是,规范与实现完全不同。所有现代实现 ECMAScript 按照定义的顺序遍历对象属性。 因此,Chrome 团队认为这是一个错误,并将对其进行修复。

所有浏览器都尊重定义顺序 with the exception of Chrome 和 Opera,它们对每个非数字属性名称都执行此操作。在这两个浏览器中,属性被按顺序拉到第一个非数字属性之前(这与它们如何实现数组有关)。 Object.keys 的顺序也相同。

这个例子应该清楚会发生什么:

var obj = {
  "first":"first",
  "2":"2",
  "34":"34",
  "1":"1",
  "second":"second"
};
for (var i in obj) { console.log(i); };
// Order listed:
// "1"
// "2"
// "34"
// "first"
// "second"

这方面的技术细节不如它随时可能改变的事实重要。不要指望事情会保持这种状态。

简而言之:如果顺序对您很重要,请使用数组。

【讨论】:

  • 并非如此。 Chrome 没有实现与其他浏览器相同的顺序:code.google.com/p/v8/issues/detail?id=164
  • “如果顺序对您很重要,请使用数组”:当您使用 JSON 时会怎样?
  • @HM2K,同样的事情,规范说“一个对象是零个或多个名称/值对的无序集合。” JSON 不是 JavaScript:服务器不需要(也可能不会)尊重您处理它的顺序。
  • Firefox,因为它的 21 版本似乎不再尊重插入顺序。
  • 这个答案在 ES2015 中是错误的。
【解决方案2】:

一年后再碰这个……

现在是2012,各大浏览器仍然不同:

function lineate(obj){
    var arr = [], i;
    for (i in obj) arr.push([i,obj[i]].join(':'));
    console.log(arr);
}
var obj = { a:1, b:2, c:3, "123":'xyz' };
/* log1 */  lineate(obj);
obj.a = 4;
/* log2 */  lineate(obj);
delete obj.a;
obj.a = 4;
/* log3 */  lineate(obj);

gisttest in current browser

Safari 5、Firefox 14

["a:1", "b:2", "c:3", "123:xyz"]
["a:4", "b:2", "c:3", "123:xyz"]
["b:2", "c:3", "123:xyz", "a:4"]

Chrome 21、Opera 12、Node 0.6、Firefox 27

["123:xyz", "a:1", "b:2", "c:3"]
["123:xyz", "a:4", "b:2", "c:3"]
["123:xyz", "b:2", "c:3", "a:4"]

IE9

[123:xyz,a:1,b:2,c:3] 
[123:xyz,a:4,b:2,c:3] 
[123:xyz,a:4,b:2,c:3] 

【讨论】:

  • 这里有什么问题?正如规范所说,它是键/值对的无序列表。
  • nickf:实现是正确的,因为它们按照规范所说的那样做。我想每个人都同意 javascript 规范是......好吧,我不想使用“错误”这个词,“非常愚蠢和烦人”怎么样? :P
  • @boxed:将对象视为哈希映射(表/其他)。在大多数语言(Java、Python 等)中,这些类型的数据结构都没有排序。因此,这在 JavaScript 中也是如此也就不足为奇了,它当然不会使规范错误或愚蠢。
  • 老实说,我不明白这个答案的意义(对不起)。属性迭代的顺序是一个实现细节,并且浏览器使用不同的 JavaScript 引擎,因此预计顺序会有所不同。这不会改变。
  • 在最先进的实现中,由于由真实数组支持,您将按数值顺序接收整数属性,并且由于隐藏的类/形状公式,将按插入顺序接收命名属性。可能会有所不同的是,它们是先列出整数属性还是先列出命名属性。 delete 的添加很有趣,因为至少在 V8 中,它会立即导致对象被哈希表备份。但是,V8 中的哈希表以插入顺序存储。这里最有趣的结果是 IE,我想知道他们做了什么样的丑陋来实现它......
【解决方案3】:

来自ECMAScript Language Specification,第 12.6.4 节(在for .. in 循环上):

枚举属性的机制取决于实现。枚举顺序由对象定义。

以及第 4.3.3 节(“对象”的定义):

它是一个无序的属性集合,每个属性都包含一个原始值、对象或函数。存储在对象属性中的函数称为方法。

我猜这意味着您不能依赖在 JavaScript 实现中以一致的顺序枚举的属性。 (无论如何,依赖于语言的特定实现细节都是不好的风格。)

如果您想定义您的订单,您将需要实现一些定义它的东西,例如在使用它访问对象之前排序的键数组。

【讨论】:

    【解决方案4】:

    for/in 枚举的对象元素是没有设置 DontEnum 标志的属性。 ECMAScript,又名 Javascript,标准明确指出“对象是属性的无序集合”(参见 http://www.mozilla.org/js/language/E262-3.pdf 第 8.6 节)。

    假设所有 Javascript 实现都将按声明顺序进行枚举,这将不符合标准(即安全)。

    【讨论】:

    • 这就是他问这个问题的原因,而不是仅仅假设:p
    【解决方案5】:

    在删除属性方面,迭代顺序也很混乱,但在这种情况下仅限于 IE。

    var obj = {};
    obj.a = 'a';
    obj.b = 'b';
    obj.c = 'c';
    
    // IE allows the value to be deleted...
    delete obj.b;
    
    // ...but remembers the old position if it is added back later
    obj.b = 'bb';
    for (var p in obj) {
        alert(obj[p]); // in IE, will be a, bb, then c;
                       // not a, c, then bb as for FF/Chrome/Opera/Safari
    }
    

    如果http://code.google.com/p/v8/issues/detail?id=164 的讨论有任何迹象,那么更改规范以修复迭代顺序的愿望似乎在开发人员中很受欢迎。

    【讨论】:

      【解决方案6】:

      在 IE6 中,不保证顺序。

      【讨论】:

      • 嗯,我的意思是...,它是 IE6。
      【解决方案7】:

      订单不可信。 Opera 和 Chrome 都返回无序的属性列表。

      <script type="text/javascript">
      var username = {"14719":"A","648":"B","15185":"C"};
      
      for (var i in username) {
        window.alert(i + ' => ' + username[i]);
      }
      </script>
      

      上面的代码在 Opera 中显示了 B、A、C,在 Chrome 中显示了 C、A、B。

      【讨论】:

        【解决方案8】:

        这本身并没有回答问题,而是为基本问题提供了解决方案。

        假设您不能依赖顺序来保存,为什么不使用具有键和值作为属性的对象数组?

        var myArray = [
            {
                'key'   : 'key1'
                'value' : 0
            },
            {
                'key'   : 'key2',
                'value' : 1
            } // ...
        ];
        

        现在,由您来确保密钥是唯一的(假设这对您也很重要)。同样,直接寻址也发生了变化,for (...in...) 现在将索引返回为“键”。

        > console.log(myArray[0].key);
        key1
        
        > for (let index in myArray) {console.log(myArray[index].value);}
        0
        1
        
        请参阅CodePen 上 JDQ (@JDQ) 的 Pen for (...in...) addressing in order

        【讨论】:

          【解决方案9】:

          正如其他答案所述,不,不能保证顺序。

          如果你想按顺序迭代,你可以这样做:

          let keys = Object.keys(myObject);
          for (let key of keys.sort()) {
            let value = myObject[key];
            
            // Do what you want with key and value 
          }
          

          请注意,就性能而言,这并不是最优的,但是当您想要一个漂亮的字母显示时,这就是价格。

          【讨论】:

            猜你喜欢
            • 2016-11-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-08-05
            相关资源
            最近更新 更多