原生Array.prototype.forEach()
非常慢。我不知道为什么。另外,我不知道为什么在Underscore.js的_.each()
中实现了它。我认为人们通常认为,如果它在一个库中,或者它是由浏览器实现的,那么在被证明不正确/有效之前它是正确/有效的。
以下是证据:
http://jsperf.com/native-vs-implmented-0
如果我们只是从underscore, _.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;
}
}
}
};
我打算问下划线团队,但我想确认我没有错过任何明显的事情。
这是否会提升效果?性能是否受到期待,希望有一天会更快?是否可以减少有用的功能来提高性能?
答案 0 :(得分:7)
看看Lo-Dash。作者在研究这些性能问题方面做了大量工作,特别是在this blog post和this video中。
如果性能是你的目标,那么通过编写自己的迭代器来完成最好的服务,只需要你需要的东西,而不是别的 - 那么它应该表现得非常好。例如,在许多情况下,您只需要数组元素而不是索引。您绝对不需要使用.call()
将数组元素作为this
传递给回调,因为它也作为命名参数传递。你真的需要在回调中使return false;
终止循环吗?我记不清上次使用它了。所以它可以很简单:
function eachElement( array, callback ) {
for( var i = 0, n = array.length; i < n; ++i ) {
callback( array[i] );
}
}
为了速度而难以击败它。
当您需要索引时,您可以拥有单独的版本:
function eachElementAndIndex( array, callback ) {
for( var i = 0, n = array.length; i < n; ++i ) {
callback( array[i], i );
}
}
您可以使用较短的功能名称;为了清楚起见,我只是使用这些长名称。可能会发现eachElementAndIndex()
足够快,因此您可以将其称为each()
并完成它。
值得注意的是,正如@YuryTarabanko在上面的评论中指出的那样,这些简化的迭代器不符合Array.prototype.forEach()
的规范。除了不将数组元素作为this
传递并且不将整个数组作为第三个参数传递之外,它们不会检查缺少的元素,并将调用从0
到{的每个数组索引的回调。 {1}}无论数组元素是否实际存在。例如。
事实上,Underscore.js的array.length - 1
在这方面并不一致。当它回退到_.each()
时,它会跳过缺少的元素,但是当它使用自己的Array.prototype.forEach()
循环时,它会包含它们。
尝试转到underscorejs.org并将其粘贴到Chrome控制台中:
for
记录:
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' );
在许多(可能是大多数)案例中,缺少的元素并不重要。您要么使用您创建的数组,要么使用JSON数组,并且您知道它没有任何缺少的元素。特别是对于JSON数组,这绝不是问题,因为JSON没有办法制作缺少元素的数组。 JSON数组可以包含 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
元素,但`.forEach()包含与其他元素类似的元素。
只要您的简化迭代器执行您想要的操作并且未命名为null
,您就不必担心它符合您自己以外的任何规范。但是,如果您确实需要跳过缺少的数组元素,请在您自己的代码中考虑这一点,或使用标准Array.prototype.forEach
。
另外,为了简化您正在进一步查看的下划线代码,因为我们讨论的是.forEach()
,这意味着我们只讨论.forEach()
Array
部分1}},而不是_.each()
部分。所以感兴趣的实际代码路径就是这样:
Object