如何简化和加速_.each?

3 javascript underscore.js

原生Array.prototype.forEach()非常慢.我不知道为什么.另外,我不知道为什么它在实现Underscore.js_.each().我认为人们通常认为,如果它在一个库中,或者它是由浏览器实现的,那么在证明不正确/有效之前它是正确/有效的.

这是证明:

http://jsperf.com/native-vs-implmented-0

如果我们只是从下划线中删除本机调用_.each,我们得到:

  var each = _.each = _.forEach = function(obj, iterator, context) {
    if (obj == null) return;
    if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        if (iterator.call(context, obj[i], i, obj) === breaker) return;
      }
    } else {
      for (var key in obj) {
        if (_.has(obj, key)) {
          if (iterator.call(context, obj[key], key, obj) === breaker) return;
        }
      }
    }
  };
Run Code Online (Sandbox Code Playgroud)

我打算问下划线团队,但我想验证我没有错过任何明显的东西.

这是一个有效的减少,将提高性能?性能是否受到期待,希望有一天会更快?这种减少可以在不失去有用功能的情况下提高性能吗?

Mic*_*ary 7

看看Lo-Dash.作者已经做了很多工作来研究这些性能问题,特别是在这篇博客文章这个视频中.

如果性能是你的目标,那么通过编写自己的迭代器来完成最好的服务,只需要你需要的东西,而不是其他东西 - 那么它应该表现得非常好.例如,在许多情况下,您只需要数组元素而不是索引.您绝对不需要使用.call()将数组元素传递给回调this,因为它也作为命名参数传递.你真的需要return false;在回调中终止循环吗?我记不清上次使用它了.所以它可以很简单:

function eachElement( array, callback ) {
    for( var i = 0, n = array.length;  i < n;  ++i ) {
        callback( array[i] );
    }
}
Run Code Online (Sandbox Code Playgroud)

为了速度而难以击败它.

您需要索引时可以使用单独的版本:

function eachElementAndIndex( array, callback ) {
    for( var i = 0, n = array.length;  i < n;  ++i ) {
        callback( array[i], i );
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用较短的函数名称; 为了清楚起见,我只是使用这些长名称.它可能会变得eachElementAndIndex()足够快,所以你可以调用它each()并完成它.

值得注意的是,正如@YuryTarabanko在上面的评论中指出的那样,这些简化的迭代器不符合规范Array.prototype.forEach().除了没有通过数组元素this,而不是传递整个数组作为第三个参数,他们不检查缺少的元素,并要求每个数组索引回调从0通过array.length - 1,无论是否实际存在的数组元素.例如.

事实上,Underscore.js _.each()在这方面是不一致的.当它回落时Array.prototype.forEach(),它会跳过丢失的元素,但当它使用自己的for循环时,它会包含它们.

尝试使用underscorejs.org并将其粘贴到Chrome控制台:

function eachElementAndIndex( array, callback ) {
    for( var i = 0, n = array.length;  i < n;  ++i ) {
        callback( array[i], i );
    }
}

function log( e, i ) {
    console.log( i + ':', e );
}

var array = [];
array[2] = 'two';

console.log( 'eachElementAndIndex():' );
eachElementAndIndex( array, log );

console.log( '_.each() using native .forEach():' );
_.each( array, log );

console.log( '_.each() using its own loop:' );
var saveForEach = Array.prototype.forEach;
delete Array.prototype.forEach;
_.each( array, log );
Array.prototype.forEach = saveForEach;

console.log( 'Done' );
Run Code Online (Sandbox Code Playgroud)

它记录:

eachElementAndIndex():
0: undefined
1: undefined
2: two
_.each() using native .forEach():
2: two
_.each() using its own loop:
0: undefined
1: undefined
2: two
Done
Run Code Online (Sandbox Code Playgroud)

在许多(可能是大多数)案例中,缺失的元素并不重要.您要么使用您创建的数组,要么使用JSON数组,并且您知道它没有任何缺少的元素.特别是对于JSON数组,这绝不是问题,因为JSON没有办法制作缺少元素的数组.JSON数组可以包含null元素,但`.forEach()包含与其他元素类似的元素.

只要您的简化迭代器完成您想要的并且没有命名Array.prototype.forEach,您就不必担心它符合您自己以外的任何规范.但是,如果您确实需要跳过缺少的数组元素,请在您自己的代码中考虑它或使用标准.forEach().

另外,为了简化您正在进一步研究的下划线代码,因为我们正在讨论.forEach(),这意味着我们只谈论Array部分_.each(),而不是Object部分.所以感兴趣的实际代码路径就是这样:

  var each = _.each = _.forEach = function(obj, iterator, context) {
    if (obj == null) return;
    if (obj.length === +obj.length) {
      for (var i = 0, l = obj.length; i < l; i++) {
        if (iterator.call(context, obj[i], i, obj) === breaker) return;
      }
    }
  };
Run Code Online (Sandbox Code Playgroud)