在JavaScript中,遍历项目的最佳方法是什么?

12

我最近了解了一种在JavaScript中遍历数组的不同方法。

我过去常常这样编写循环:

for (var len = 0; len < testData.length; len++) {
  total = total + testData[len];
}

我读到一些这样的代码:

for (var len = 0; testData[len]; len++) {
  total = total + testData[len];
}

我想知道这些方法的性能如何,所以我使用了jsPerf进行测试。结果非常惊人。我原本以为第二种方法会比第一种稍微快一点,但实际上它要快得多。

我是否忽略了什么缺点?或者这是遍历列表项的最佳方法。

更新:

灰色状态正在到来Diode指出了测试用例中的一个简单缺陷,使得它看起来比实际更快。

在修正错误后,这是最快的方法:

var datalen = testData.length;
for (var len = 0; len <datalen; len++) {
     total = total + testData[len];
}

更新2:

在更多的浏览器中测试后,这个测试用例又有了不同的方向。只有在Chrome和Opera中,普通的for循环是最快的。在其他所有浏览器中,Shmiddty的方法稍微快一点。

var i = testData.length, sum=0;
while (i--){
    sum += testData[i];
}

2
当你说“最好”的时候,你在寻找什么?速度、代码可读性、可靠性?你正在循环哪种类型的数组? - Simon Forsberg
顺便说一下,我已经添加了 jsPerf 的链接。 - Sorskoot
@Sorskoot:如果你能确定所有的项都是对象,那么这是一个好的、安全的方法。 - gray state is coming
1
但是你的JSPerf测试非常有缺陷。它似乎很快,因为它在第一次迭代时就停止了,因为有一个0值。将初始化更改为从1开始而不是0,然后进行比较。for (var i = 1; i < 1000; i++) { - gray state is coming
1
修正后的JSPerf测试显示了非常不同的结果。http://jsperf.com/for-until-length-vs-until-undefined/7 - gray state is coming
显示剩余7条评论
6个回答

13

我认为第一个形式更好。第二个形式存在一些问题。如果您有一个包含falsy值的稀疏数组呢?例如:var testData = [ "text", undefined, false, 0, 5 ];

我也预计第一个形式性能更好,特别是如果您“缓存”testData.length的值,就像这样:

var i, len = testData.length;
for (i = 0; i < len; i += 1) {
  total = total + testData[i];
}

@ParitoshSingh 这个问题在于 undefined === void 0 仍然为真。 - user166390
@ParitoshSingh:你可以定义一个值为“undefined”的属性。但是除此之外,如果你要进行这样的比较,那么最好将索引与长度进行比较。 - gray state is coming
是的,所以在最后一个元素之后,下一个元素将是未定义的,我想这就是我们要检查的。 - Paritosh Singh
@ParitoshSingh 我已经更新了帖子,并提供了一个打破 == void 0(也称为 == undefined)的示例测试数据。我通过将 undefined 添加为正在迭代的数组的值来实现这一点。 - user166390
1
在 jsPerf 测试用例中,我比较了缓存和非缓存版本的 length。 缓存长度确实有利于提高速度。 - Sorskoot
显示剩余3条评论

4
更新:我错了,forEach非常慢!看来循环更好。
您应该使用在ECMAScript第五版中定义的forEach
testData.forEach(function(val,i,arr){
    total += val;
});

以下是参数说明:

  • val 是当前值
  • i 是当前索引
  • arr 是数组

不必全部使用:

testData.forEach(function(val){
    total += val;
});

若浏览器不支持此功能,则可使用该shim:
if(!Array.prototype.forEach){
    Array.prototype.forEach = function(fn, scope) {
        for(var i = 0, len = this.length; i < len; ++i) {
            fn.call(scope || this, this[i], i, this);
        }
    }
}

请查看Array.forEach了解更多相关信息。


我完全忘记了forEach。我把它添加到了jsPerf中。虽然这是一个显而易见的解决方案,但事实证明这是迭代最慢的方式。 - Sorskoot
@Sorskoot 奇怪...我本以为它应该更快,因为它是原始代码。也许是因为它需要一个函数,所以作用域链增加了。有趣的是,我正在开始使用 forEach 的项目,我会将其改成循环,感谢你的 jsperf 比较。 - Oriol
1
@Oriol:这与作用域链无关;JavaScript中的范围是静态的。使用 forEach 的问题在于函数调用不是免费的--事实上,正如您所看到的,它是相当昂贵的。 - josh3736

3
你也可以这样做一个循环:
var i = testData.length;

while (i--){
    console.log(testData[i]);
}

请注意它是以倒序遍历数组。

或者,对于求和:

var i = testData.length, sum=0;

while (i--){
    sum += testData[i];
}

我将其添加到测试用例中。令人惊讶的是,常规的for循环更快。 - Sorskoot
看起来非常接近。http://jsperf.com/for-until-length-vs-until-undefined/9 - Halcyon
我尝试了一些其他浏览器,某些浏览器中这种方式实际上是最快的... - Sorskoot
这些测试用例适用于长度为1000的数组。如果列表显着较短,则测试结果会发生变化。我无法解释任何这些性能结果。 - Halcyon
可能是由于每个 JavaScript 引擎中的优化不同所致。 - Shmiddty

2

根据Shmiddt的回答,您可以使用一个反向循环。在Firefox上,它似乎比while反向循环稍微快一点,在Chrome上它们则相等:

var total = 0;
for (var len = testData.length-1; len >=0; len--) {
  total += testData[len];
}

测试: http://jsperf.com/for-until-length-vs-until-undefined/10

该链接是一个关于it技术的性能测试页面。该测试主要比较使用数组长度和undefined作为循环边界的for循环效率。

1
关于性能方面,testData[len] 只是检查元素是否被定义且不为 null/false/"falsish",这比比较更快,特别是对于每次迭代检索 testData.length。
因此第二种方法更快,但并不更可靠 - 正如 Frits van Campen 所述。

1

您的设置代码插入了0,这会导致循环在第一个条件下中断

var testData = [];
for (var i = 0; i < 1000; i++) {
  testData.push(i);  // pushes 0 to `testData`
}


for (var len = 0; testData[len]; len++) {   // loop breaks on first condition-check as testData[0] is 0.
  total = total + testData[len];
}

将设置代码更改为以下内容,看看有何不同

var testData = [];
for (var i = 0; i < 1000; i++) {
  testData.push(i+1);
}

http://jsperf.com/for-until-length-vs-until-undefined/6


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