为什么lodash.each比原生forEach更快?

我试图find用自己的作用域运行for循环的最快方法。 我比较的三种方法是:

var a = "t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t".split(); // lodash .each -> 1,294,971 ops/sec lodash.each(a, function(item) { cb(item); }); // native .forEach -> 398,167 ops/sec a.forEach(function(item) { cb(item); }); // native for -> 1,140,382 ops/sec var lambda = function(item) { cb(item); }; for (var ix = 0, len = a.length; ix < len; ix++) { lambda(a[ix]); } 

这是在OS X的Chrome 29上。你可以在这里自己运行testing:

http://jsben.ch/#/BQhED

每个东西.each快两倍? 而且,它是如何比平原更快的呢? 巫术? 黑魔法?

_.each()[].forEach()不完全兼容。 看下面的例子:

 var a = ['a0']; a[3] = 'a3'; _.each(a, console.log); // runs 4 times a.forEach(console.log); // runs twice -- that's just how [].forEach() is specified 

http://jsfiddle.net/BhrT3/

所以lodash的实现缺lessif (... in ...)检查,这可能解释性能差异。


正如在上面的评论中指出的,与native的区别主要是由testing中的附加函数查找引起的。 使用此版本获得更准确的结果:

 for (var ix = 0, len = a.length; ix < len; ix++) { cb(a[ix]); } 

http://jsperf.com/lo-dash-each-vs-native-foreach/15

http://kitcambridge.be/blog/say-hello-to-lo-dash/

开发人员解释说(在这里和video中),原生forEach的相对速度因浏览器而异。 仅仅因为forEach是本地的,并不意味着它比用forwhile构build的简单循环更快。 首先, forEach必须处理更多特殊情况。 其次, forEach使用callback函数,调用函数的(潜在)开销等。

chrome是特别的(至less对开发商来说)是相对缓慢的。 所以对于那个浏览器,lo-dash使用它自己的简单while循环来获得速度。 因此,你看到的速度优势(但其他人不)。

通过巧妙地select本地方法 – 如果已知在给定环境中快速执行,则仅使用本地实现–Lo-Dash可避免与本机相关的性能成本和一致性问题。

是的,lodash /下划线每个都没有与.forEach相同的语义。 有一个微妙的细节,将使该function真的很慢,除非引擎可以检查稀疏的arrays没有getter快速。

这将是99%的规范兼容和运行速度相同的情况下, 每个在V8的常见情况下:

 function FastAlmostSpecForEach( fn, ctx ) { "use strict"; if( arguments.length > 1 ) return slowCaseForEach(); if( typeof this !== "object" ) return slowCaseForEach(); if( this === null ) throw new Error("this is null or not defined"); if( typeof fn !== "function" ) throw new Error("is not a function"); var len = this.length; if( ( len >>> 0 ) !== len ) return slowCaseForEach(); for( var i = 0; i < len; ++i ) { var item = this[i]; //Semantics are not exactly the same, //Fully spec compliant will not invoke getters //but this will.. however that is an insane edge case if( item === void 0 && !(i in this) ) { continue; } fn( item, i, this ); } } Array.prototype.fastSpecForEach = FastAlmostSpecForEach; 

通过首先检查未定义,我们根本不惩罚循环中的普通数组。 引擎可以使用它的内部来检测奇怪的数组,但V8没有。

这里有一个更新的链接(大约在2015年),显示了比较所有三个for(...)Array.forEach_.each的性能差异: https : _.each

注意:因为我没有足够的分数来评论接受的答案,所以放在这里。