JavaScript for...in vs for

472

你认为for...in和for循环有很大的区别吗?你更喜欢使用哪种“for”?为什么?

假设我们有一个关联数组的数组:

var myArray = [{'key': 'value'}, {'key': 'value1'}];

所以我们可以进行迭代:

for (var i = 0; i < myArray.length; i++)

并且:

for (var i in myArray)

我没有看到很大的差别。有性能问题吗?


13
请注意,我们在JS 1.6中也有myArray.forEach(callback[, thisarg])方法。 - Benji XVI
14
@Benji array.forEach 实际上是 ES5 中的一个方法。 - mikemaccana
2
在 for-in 循环中,您需要一个条件语句,看起来像这样:if(myArray.hasOwnProperty(i)){true} - Eric Hodonsky
6
['foo', 'bar', 'baz'].forEach(function(element, index, array){ console.log(element, index, array); });除了 IE8 及以下版本外,这个语法基本上可以在任何地方使用,并且它是目前最优雅的语法。 - Jon z
5
ECMAScript 6中,还有一个名为for...of的语句,例如:for (let i of myArray) console.log(i);。它可以遍历一个可迭代对象(如数组),并使用变量i来迭代该对象中的每个元素。在循环体内部,您可以对每个元素执行任何操作。 - Vitalii Fedorenko
23个回答

551

选择应基于哪种习语被最好理解。

使用以下方式迭代数组:

for (var i = 0; i < a.length; i++)
   //do stuff with a[i]

使用一个对象作为关联数组时,需要使用以下方式进行迭代:

for (var key in o)
  //do stuff with o[key]

除非您有令人震惊的理由,否则请坚持使用已经建立的模式。


39
值得一提的是,在使用过滤语句时,使用for...in循环非常好。Object对象有一个方便的方法"obj.hasOwnProperty(member)",可以检查迭代器返回的成员是否实际上是对象的成员。参见:http://javascript.crockford.com/code.html - Damir Zekić
58
在另一个答案中也提到,对于数组来说,使用"for...in"会遍历所有的数组属性和方法,因此你应该仅在遍历对象属性时使用"for...in"。否则,请使用"for(i=0; i<something; i++)"。 - Denilson Sá Maia
9
@UpTheCreek: 当数组确实是 HTMLDOM 返回的内容时,这绝对是正确的。然而,我想知道一个标准的 JavaScript 数组需要有多大,你才能看到明显的差异?个人认为,在没有证明需要做出不同的操作之前,应该尽可能保持代码简单。 - AnthonyWJones
2
@Pichan 我认为你的for循环条件应该是i < l而不是i < a - Max Nanasy
刚发现一个有趣的 bug。当迭代整数数组时,for ... in 语句会将整数转换为字符串。 - Raynal Gobel
显示剩余4条评论

162

道格拉斯·克罗克福德在JavaScript: The Good Parts(第24页)中建议避免使用for in语句。

如果您使用for in循环遍历对象中的属性名称,则结果不会按顺序排列。更糟糕的是:您可能会得到意外的结果;它包括从原型链继承的成员和方法的名称。

除了属性之外,可以使用.hasOwnProperty过滤掉所有内容。此代码示例执行最初想要的操作:

for (var name in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, name)) {
        // DO STUFF
    }
}

71
使用 for...in 循环遍历对象属性是完全适当的。但是,对于循环遍历数组元素来说,它是不合适的。如果你不能理解这些情况之间的区别,那么确实应该避免使用 for...in;否则,就可以使用它了。 - Shog9
8
强调这一事实是“未订购”!这可能是个大问题,而且很难发现这个漏洞。 - Jason
4
+1表示“它包括从原型链继承的成员和方法名称。” 如果有人在Prototype已加载的情况下使用您的代码,您会感到很有趣(即使您的代码实际上没有使用它)。 - ijw
13
请不要忘记声明name变量:for(var name in object)...,否则,如果该代码位于一个函数中,name变量最终会成为全局对象的属性(对未声明的标识符进行赋值会导致这种情况),同时在新的ECMAScript 5严格模式下,该代码将会抛出ReferenceError - Christian C. Salvadó
6
@Nosredna:有关Chrome迭代顺序的问题,由John Resig提交,并被标记为WontFix。 http://code.google.com/p/chromium/issues/detail?id=883 。即使在Chrome之前,如果您删除然后再添加属性,各浏览器的迭代顺序也不相同。另外,据说IE 9行为很像Chrome(可能是为了提高速度)。所以,请停止传播不准确的信息,继续依赖它会非常天真。 - Ruan Mendes
显示剩余4条评论

62

须知 - jQuery 用户


jQuery 的 each(callback) 方法默认使用 for( ; ; ) 循环,只有在长度为 undefined 时才会使用 for( in ) 循环。

因此,在使用该函数时可以安全地假设正确的顺序。

例如:

$(['a','b','c']).each(function() {
    alert(this);
});
//Outputs "a" then "b" then "c"

使用这种方法的缺点是,如果你正在进行一些非 UI 逻辑,则你的函数将不太容易移植到其他框架中。each() 函数可能最好保留在与 jQuery 选择器一起使用时,否则可能建议使用 for( ; ; )


5
可以使用 http://documentcloud.github.com/underscore/,其中包括 _.each 和许多其他有用的函数。 - w00t
1
这是否意味着如果我的对象中有length属性,$.each会失败?例如,x = {a:“1”,b:“2”,length:3}。 - Onur Topal

29

不同的浏览器和循环方式会对性能产生影响。

例如:

for (var i = myArray.length-1; i >= 0; i--)

在某些浏览器上,速度几乎是下列内容的两倍:

for (var i = 0; i < myArray.length; i++)

但是,除非你的数组非常大或者你经常循环遍历它们,否则所有的数组操作都足够快。我严重怀疑数组循环是你的项目(或任何其他项目)的瓶颈。


5
在循环之前将"myArray.length"存储到一个变量中,会使性能差异消失吗?我猜是“是”。 - Tomalak
3
“myArray.length”是一个属性,而不是对象上的方法——没有进行任何计算来确定它的值。将它的值存储在一个变量中将完全无效。 - jason
3
有的。属性不是变量;它们具有获取/设置代码。 - ste
12
我倾向于使用for(var i = myArray.length; i--;) - Kevin
2
编写代码时要注重清晰易读,而非为了在当前某些浏览器中使用荒谬巨大的数组而稍微提高一点运行速度。优化可能会改变,但你的代码的可读性(或缺乏可读性)不会改变。编写易于他人跟随的代码,让优化器在它们自己的时间内追赶即可。 - aroth
显示剩余8条评论

26

2
它是做什么的?它是否存在其他帖子中提到的问题(循环遍历属性而不是数组元素)?另外,由于IE8不支持它,所以说它得到广泛支持有点牵强。 - Rauni Lillemets
2
尽管*nix用户鄙视它,但IE8是大多数Windows7用户使用的浏览器。这占据了浏览器市场的巨大份额。 - sbartell
2
@Rauni -- 我理解你的观点,但是对于桌面设备而言,IE浏览器的市场份额不到40%,根据http://en.wikipedia.org/wiki/Usage_share_of_web_browsers#Summary_table,以及http://marketshare.hitslink.com/browser-market-share.aspx?qprid=2&qpcustomd=0和其他网站的数据,至少有8%的浏览器是IE 9。换句话说,大约70%的桌面浏览器支持Array.forEach,因此我认为“广泛支持”并不过分。我没有检查过,但移动端(在WebKit和Opera浏览器上)的支持率可能更高。显然,地理位置会有相当大的差异。 - Sam Dutton
1
谢谢更新。我同意可以说它是“广泛支持的”。唯一的问题是,如果用户使用这个JS方法,他/她仍然需要编写备用方法以防不支持。 - Rauni Lillemets
1
@Rauni--你可以使用es5-shim和es6-shim自动提供备用方法。https://github.com/es-shims/es5-shim - Michiel van der Blonk
@MichielvanderBlonk - 2015年我能找到的仍在使用IE7或更早版本的人最慷慨的估计是来自NetApplications的15%。大多数其他来源将其列为3%或更少 - 这些数据可以忽略。 - ArtOfWarfare

24

2012年的最新答案更新 所有主流浏览器--Chrome、Firefox、IE9、Safari和Opera--都支持ES5原生的array.forEach。

除非你有某些理由需要支持IE8原生(记住这些用户可以提供ES5-shim或Chrome Frame,这将提供适当的JS环境),否则使用语言本身的正确语法更加干净:

myArray.forEach(function(item, index) {
    console.log(item, index);
});

array.forEach()的完整文档在MDN上。


1
你应该真正记录回调函数的参数:第一是元素值,第二是元素索引,第三是正在遍历的数组。 - drewish
我明白你的意思,但在这种情况下过于简化会掩盖了所有可能性。同时拥有索引和值意味着它可以替代for...in和for each...in两者——而且还有额外的好处,你不必记住哪个迭代键或值。 - drewish
在企业环境中,仍有大量人员使用IE 6、7和8。我肯定会确保你的实现选择适合你的受众。 - Cᴏʀʏ
1
@Cory:ES5的forEach可以很容易地添加到传统的ES3浏览器中。更少的代码就是更好的代码。 - mikemaccana
2
@nailer 这个能在数组和对象之间互换使用吗? - hitautodestruct
1
@hitautodestruct 它是Array的原型的一部分,而不是Object的。通常在社区中,非Array对象仍然使用'for ( var key in object ) {}'进行迭代。 - mikemaccana

14

使用forEach跳过原型链

仅作为对@nailer的答案的补充,使用Object.keys和forEach可以避免在不使用hasOwnProperty的情况下迭代原型链。

var Base = function () {
    this.coming = "hey";
};

var Sub = function () {
    this.leaving = "bye";
};

Sub.prototype = new Base();
var tst = new Sub();

for (var i in tst) {
    console.log(tst.hasOwnProperty(i) + i + tst[i]);
}

Object.keys(tst).forEach(function (val) {
    console.log(val + tst[val]);
});

2
该死,这太狡猾了。为了看到这个结果,我读了50篇其他的帖子也是值得的。obj={"pink":"ducks", red: "geese"}; Object.keys(obj) === ["pink", "red"] - Orwellophile

14

当数组稀疏时,这两者并不相同。

var array = [0, 1, 2, , , 5];

for (var k in array) {
  // Not guaranteed by the language spec to iterate in order.
  alert(k);  // Outputs 0, 1, 2, 5.
  // Behavior when loop body adds to the array is unclear.
}

for (var i = 0; i < array.length; ++i) {
  // Iterates in order.
  // i is a number, not a string.
  alert(i);  // Outputs 0, 1, 2, 3, 4, 5
  // Behavior when loop body modifies array is clearer.
}

14

我赞同选择迭代方法应该根据你的需求。我建议你不要使用for in结构遍历原生Array,因为它速度较慢,并且正如Chase Seibert所指出的那样,与Prototype框架不兼容。

如果你使用JavaScript,那么就有一个关于不同循环风格的优秀基准测试,你一定要看一下。不要进行早期优化,但你应该将这些东西放在脑后。

我会使用for in获取对象的所有属性,这在调试脚本时特别有用。例如,当我探索陌生的对象时,我喜欢随手写下这行:

l = ''; for (m in obj) { l += m + ' => ' + obj[m] + '\n' } console.log(l);

它将整个对象(包括方法体)的内容转储到我的Firebug日志中。非常方便。


链接已经失效。如果有其他链接,我一定想看看这个基准测试。 - Billbad
原型中的foreach循环会中断吗?由于它现在得到了普遍支持,这是原型应该解决的问题。 - mvrak
链接已过期 - “404 | 未找到 | 您正在寻找的内容不再在此页面上”。 - Pang

7

我做了一些事情。

相关的内容与IT技术有关。
function foreach(o, f) {
 for(var i = 0; i < o.length; i++) { // simple for loop
  f(o[i], i); // execute a function and make the obj, objIndex available
 }
}

这是使用它的方法
这适用于数组和对象(例如HTML元素列表)

foreach(o, function(obj, i) { // for each obj in o
  alert(obj); // obj
  alert(i); // obj index
  /*
    say if you were dealing with an html element may be you have a collection of divs
  */
  if(typeof obj == 'object') { 
   obj.style.marginLeft = '20px';
  }
});

我刚刚完成了这个,欢迎提出建议 :)


非常棒 - 相当简单! - Chris

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