$.each() 和 for() 循环的区别 - 以及性能

75

这些只是我一直在想的问题,也许有人能给我更多的见解,我也会分享我到目前为止所注意到的东西!

首先,我想知道是否有任何好处或原因使用:

$('element').each(function (i, el) { });

-- 相对于 --

$.each($('element'), function (i, el) { });

从jQuery文档中可以看不出哪个更好或为什么选择其中之一(也许您知道一个示例或其他可以执行的操作)。

但更重要的是我关心的是速度问题

// As opposed to $.each() looping through a jQuery object
// -- 8x faster 
for (var i = 0, $('.whatever').length; i < len; i++) {
    $('.whatever')[i] // do stuff
}
如果你查看这个jsFiddle DEMO,你会发现无论使用哪种方法速度上的差异基本相同,但更重要的是我觉得我应该总是使用for()循环... 我只是在进行单元测试(循环遍历5个不同的情景函数,50000次),简单地循环一堆列表项,并设置一个data-newAttr属性,没有什么特别的。


问题 :: 我想问的最大的问题是,为什么不始终在迭代对象时使用for循环??使用$.each()有什么意义吗?即使遍历jQuery对象,您是否仍然使用for()循环?

jsFiddle DEMO here

Function type:                  Execution Time:
_testArea.each() + $(this)               1947   <-- using $(this) slows it down tremendously
$.each()         + $(this)               1940
_testArea.each() + el(plain JS)           458   <-- using the Element speeds things up
$.each()         + el(plain JS)           452
for() loop       + plainJS[0] iteration   236   <-- over 8x faster

这只是我的个人意见。:)


31
正如Joe Armstrong曾在Erlang邮件列表中回复类似问题时所写的那样,“尽可能编写最美丽的程序”。性能问题会找上门来。你真的会处理需要迭代50000次的页面吗?如果确实需要,那么请务必进行优化。 - Pointy
3
дёҖдёӘиҝӯд»ЈjQuery DOMе…ғзҙ зҡ„forеҫӘзҺҜдјҡжҳҜд»Җд№Ҳж ·еӯҗпјҹ - Jordan
2
在jsperf.com上自行测试。 - j08691
1
考虑到jQuery.each在内部使用for,因此总体上必须做更多的工作...根据这些数据,在这种特定情况/实现中,“大约多2倍”。由于循环内部执行了更多的工作,总体差异将会减少。(请考虑使用http://jsperf.com进行微基准测试。它是为此而设计的。) - user166390
5
.each()方法遍历jQuery对象,$.each()静态方法可以遍历各种类型的对象,例如普通对象、数组和类似数组的对象。这就是它们之间的区别。 - Šime Vidas
显示剩余5条评论
6个回答

49

.each()允许你做的一件事,用for循环无法完成,那就是链式调用

$('.rows').each(function(i, el) {
    // do something with ALL the rows
}).filter('.even').each(function(i, el) {
    // do something with the even rows
});

我试着使用你的JSFiddle来了解链式调用如何影响在必须循环遍历匹配元素子集的情况下的性能。

结果并不出乎意料,虽然我认为由于少量元素和多次循环的组合,end() 的开销有些夸大了。除此之外:纯JS循环仍然稍微快一些,但这是否能弥补使用.each()(以及链式调用)带来的可读性增加还是值得商榷的。


哇!谢谢,我甚至没有想到可以像这样链接,这本身就是一个巨大的好处!在纯JS中需要编写更多的代码,一旦你有一堆if/else语句来完成同样的事情,毫秒级的优势几乎不存在。 - Mark Pieszak - Trilon.io
1
性能不佳!我会在外部的each()函数内部进行过滤,因为each().filter()会对同一个数组进行两次处理,这样虽然不太方便但更高效。如果你预计会有大量元素,那么我建议完全避免使用each()迭代,可以参考http://jsperf.com/function-call-overhead-test了解函数调用开销。 - comeGetSome

22

使用.each()方法可以获得自动本地作用域(因为您为每个对象调用匿名函数),这意味着如果您在每次迭代中创建更多的匿名函数/闭包/事件处理程序等,您永远不必担心处理程序共享变量。也就是说,当涉及到局部作用域时,JavaScript的行为与其他语言不同,但由于您可以在任何地方声明变量,有时会让您感到困惑。

换句话说,以下代码是错误的:

var idx,el;
for (idx = 0; idx <someObjectArray.length; idx++){
   el = someObjectArray[idx]
   el.someEventHandler(function(){  
       alert( "this is element " + idx);
   }); 
}
无论这些对象中的任何一个在此循环后调用其“someEvent”(请注意,这是虚构的),警报总是会显示最后分配给idx的值,这应该是(在被调用时)someObjectArray.length;为确保保存正确的索引,你必须声明一个本地范围,创建一个变量并对该变量进行分配以供使用。
var idx,el;
for (idx = 0; idx <someObjectArray.length; idx++){
   el = someObjectArray[idx];
   (function(){
       var localidx = idx;
       el.someEventHandler(function(){  
           alert( "this is element " + localidx);
       }); 
   })();
}

正如您所看到的,这非常难看,但应该可以工作。每个事件处理程序都会得到自己的 localidx 副本。

现在将其与 .each() 进行比较。

$(someObjectArray).each(function (idx, el) { 
   el.someEventHandler(function(){  
       alert( "this is element " + idx);
   }); 
});

简单多了,不是吗?


1
非常正确!我其实忘了它已经为你做到这一点了... 我想要从中得到的最大收获就是,如果你想要简单的操作|获取/设置类型的内容,用普通的JS for() 循环是最好的选择。 对于更复杂的筛选和事件处理,你几乎需要使用 each()。很有趣... - Mark Pieszak - Trilon.io

9

jQuery.each vs for-loop

优点:jQuery.each

  • 适用于jQuery代码(链式和风格)。
  • 不必担心范围问题(对迭代器和对象的引用将是持久性的)。
  • 可普遍使用(适用于所有类型的对象和对对象键进行迭代)。

优点:for循环

  • 高效(适用于游戏/动画/大型数据集)。
  • 完全控制迭代器(跳过项目,从列表中拼接项目等)。
  • for循环将始终工作,因为不依赖于jQuery。
  • 与大多数其他类似语言相同的熟悉语法。

示例代码

这是我喜欢迭代列表的示例代码。

var list = ['a', 'b', 'c', 'd', 'e', 'f'];

jQuery.each:

$.each(list, function(i, v)
{
    // code...
});

没有闭包的for循环:

for(var i=0,v,n=list.length;i<n;i+=1)
{
    v = list[i];
    // code...
}

使用闭包的for循环:

for(var i=0,n=list.length;i<n;i+=1)
{
    (function(i, v)
    {
        // code...
    })(i, list[i]);
}

注意:我建议您仅在必要时使用闭包,而仅使用标准的 for 循环。然而,当您的代码看起来更像 jQuery 而不是 JavaScript 时,仅使用 $.each 可能更容易。如果出现性能问题,您随时可以之后再研究它。


5

我之前进行了一些简单的性能测试http://jsperf.com/forloops3。看起来,如果可能的话,坚持使用普通的旧式for循环是正确的做法 :)


4
当我访问您提供的链接时,我得到了两个数字:
$.each() + el(plain JS) 401
for() loop + plainJS[0] iteration   377

如果差别很小,那么选择最易读的一个,但是如果你有非常高的时间要求,那么你可能需要选择最快的一个。我建议您编写程序使用三种不同的方法,上述两种方法和在较新版本的JavaScript中找到的foreach,并对于那些不支持它的浏览器,您可以将其添加为原型。

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach

你知道自己的需求和程序要做什么,所以只需编写自己的测试并确保它满足你将支持的浏览器的要求。
对于你的第一个问题,我会选择使用$('element').each,因为它更易读,但这只是我的个人意见。

2
尽管对于这样微小的差距,它可能没有任何影响...我会使用“更清晰一致的版本”(无论确定是哪个版本),除非有具体的实际性能案例表明它太慢了。 - user166390
1
@pst - 小的差距可能会产生影响,如果你调用它10000次,并且这个小差距使你超出了允许的范围,这就是我所询问的问题,唯一真正了解的方法是在各种浏览器上测试这三个版本并获得一些数字。 - James Black
谢谢你,詹姆斯!在我的情况下,这绝对是一个问题,我们需要支持旧版浏览器,但现在我看到从可读性和链式方面来看,使用 each() 是有意义的,然后再转向纯粹的 JS!差异真的不存在,除非我需要它保持为 JS 对象,否则我不需要使用 $(this) - Mark Pieszak - Trilon.io

2

你可以同样轻松地使用相同的方法:$(['someItem', 'anotherItem']).each(function (i, el) { }); http://jsfiddle.net/zzVQD/。同时这并不意味着这个问题应该被踩,我在这方面进行了大量的研究、编码和努力。 - Mark Pieszak - Trilon.io
1
虽然这不是这个问题的答案,但它增加了一些知识。 - Swaps

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