79

我试图找到在自己的范围内运行 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 上。你可以在这里自己运行测试:

http://jsben.ch/BQhED

lodash 的.each速度几乎是 native 的两倍.forEach?而且,它怎么比平原快for?巫术?黑魔法?

4

4 回答 4

99

_.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 的实现缺少if (... in ...)检查,这可能解释了性能差异。


正如上面评论中所指出的,与原生的差异for主要是由测试中的附加函数查找引起的。使用此版本可以获得更准确的结果:

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

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

于 2013-09-19T00:49:17.003 回答
25

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

lo-dash 开发人员解释(此处和视频中)本机的相对速度forEach因浏览器而异。仅仅因为forEach是原生的并不意味着它比使用foror构建的简单循环更快while。一方面,forEach必须处理更多的特殊情况。其次,forEach使用回调,以及函数调用的(潜在)开销等。

chrome特别是已知(至少对于 lo-dash 开发人员而言)具有相对较慢的 forEach. 所以对于那个浏览器,lo-dash 使用它自己的简单while循环来提高速度。因此,您看到了速度优势(但其他人没有)。

通过巧妙地选择原生方法——仅在已知环境中速度快的情况下才使用原生实现——Lo-Dash 避免了与原生方法相关的性能成本和一致性问题。

于 2013-10-06T07:20:57.540 回答
18

是的,lodash/underscore 每个甚至没有与.forEach. 有一个微妙的细节会使函数非常慢,除非引擎可以快速检查没有 getter 的稀疏数组。

这将符合 99% 的规范,并且在常见情况下以与V8 中的 lodash 相同的速度运行:

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 不能。

于 2013-09-19T04:33:08.067 回答
6

这是一个更新的链接(大约 2015 年),显示了比较所有三个、 和 的性能差异for(...)Array.forEachhttps _.each: //jsperf.com/native-vs-underscore-vs-lodash

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

于 2017-07-14T20:44:28.490 回答