For 循环性能:将数组长度存储在变量中

问题描述 投票:0回答:6

考虑同一循环迭代的两个版本:

for (var i = 0; i < nodes.length; i++) {
    ...
}

var len = nodes.length;
for (var i = 0; i < len; i++) {
    ...
}

后一个版本比前一个版本快吗?

javascript performance for-loop
6个回答
41
投票

接受的答案是不正确的,因为任何像样的引擎都应该能够通过如此简单的循环体将属性负载提升到循环之外。 参见

this jsperf

- 至少在 V8 中 很有趣看到 实际上将其存储在变量中如何改变寄存器分配 - 在使用变量的代码中 sum 变量存储在堆栈上,而使用

array.length
循环代码它存储在寄存器中。我认为 SpiderMonkey 和 Opera 中也发生了类似的事情。
据作者称,

JSPerf 的使用不正确

,70% 的情况下。这里所有答案中给出的这些损坏的 jsperf 会给出误导性的结果,人们从中得出错误的结论。 一些危险信号是将代码而不是函数放入测试用例中,不测试结果的正确性或使用某种消除死代码的机制,在设置或测试用例中定义函数而不是全局函数。为了保持一致性,您需要预热- 在任何基准测试之前也启动测试函数,这样编译就不会发生在定时部分。


36
投票
更新:2015年12月16日

由于这个答案似乎仍然有很多观点,我想随着浏览器和 JS 引擎的不断发展重新审视这个问题。

我没有使用 JSPerf,而是将一些代码放在一起,使用原始问题中提到的两种方法来循环数组。我已将代码放入函数中以分解功能,就像在现实世界的应用程序中完成的那样:

function getTestArray(numEntries) { var testArray = []; for (var i = 0; i < numEntries; i++) { testArray.push(Math.random()); } return testArray; } function testInVariable(testArray) { for (var i = 0; i < testArray.length; i++) { doSomethingAwesome(testArray[i]); } } function testInLoop(testArray) { var len = testArray.length; for (var i = 0; i < len; i++) { doSomethingAwesome(testArray[i]); } } function doSomethingAwesome(i) { return i + 2; } function runAndAverageTest(testToRun, testArray, numTimesToRun) { var totalTime = 0; for (var i = 0; i < numTimesToRun; i++) { var start = new Date(); testToRun(testArray); var end = new Date(); totalTime += (end - start); } return totalTime / numTimesToRun; } function runTests() { var smallTestArray = getTestArray(10000); var largeTestArray = getTestArray(10000000); var smallTestInLoop = runAndAverageTest(testInLoop, smallTestArray, 5); var largeTestInLoop = runAndAverageTest(testInLoop, largeTestArray, 5); var smallTestVariable = runAndAverageTest(testInVariable, smallTestArray, 5); var largeTestVariable = runAndAverageTest(testInVariable, largeTestArray, 5); console.log("Length in for statement (small array): " + smallTestInLoop + "ms"); console.log("Length in for statement (large array): " + largeTestInLoop + "ms"); console.log("Length in variable (small array): " + smallTestVariable + "ms"); console.log("Length in variable (large array): " + largeTestVariable + "ms"); } console.log("Iteration 1"); runTests(); console.log("Iteration 2"); runTests(); console.log("Iteration 3"); runTests();

为了实现尽可能公平的测试,每个测试运行 5 次并对结果取平均值。我还运行了整个测试,包括生成数组 3 次。在我的机器上对 Chrome 进行测试表明,使用每种方法所花费的时间几乎相同。
重要的是要记住,这个示例有点像玩具示例,事实上,从应用程序上下文中获取的大多数示例可能会产生不可靠的信息,因为代码正在执行的其他操作可能会直接或间接影响性能。

底线

确定最适合您的应用程序的最佳方法是亲自测试! JS 引擎、浏览器技术和 CPU 技术在不断发展,因此您必须始终在应用程序的上下文中自行测试性能。还值得问问自己是否存在性能问题,如果没有,那么花在用户无法察觉的微观优化上的时间可以更好地花在修复错误和添加功能上,从而让用户更满意:)。

原答案:

后者会稍微快一些。

length

属性不会迭代数组来检查元素数量,但每次在数组上调用它时,都必须取消引用该数组。通过将长度存储在变量中,每次循环迭代都不需要取消数组引用。 如果您对 javascript 中循环数组的不同方式的性能感兴趣,请查看此

jsperf

    

根据 w3schools“减少循环中的活动”以下内容被认为是不好的代码:


4
投票

以下被认为是好的代码: var arrLength = arr.length; for (i = 0; i < arrLength; i++) {

由于访问 DOM 很慢,所以编写以下内容来测试理论:


<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>my test scripts</title> </head> <body> <button onclick="initArray()">Init Large Array</button> <button onclick="iterateArraySlowly()">Iterate Large Array Slowly</button> <button onclick="iterateArrayQuickly()">Iterate Large Array Quickly</button> <p id="slow">Slow Time: </p> <p id="fast">Fast Time: </p> <p id="access"></p> <script> var myArray = []; function initArray(){ var length = 1e6; var i; for(i = 0; i < length; i++) { myArray[i] = i; } console.log("array size: " + myArray.length); } function iterateArraySlowly() { var t0 = new Date().getTime(); var slowText = "Slow Time: " var i, t; var elm = document.getElementById("slow"); for (i = 0; i < myArray.length; i++) { document.getElementById("access").innerHTML = "Value: " + i; } t = new Date().getTime() - t0; elm.innerHTML = slowText + t + "ms"; } function iterateArrayQuickly() { var t0 = new Date().getTime(); var fastText = "Fast Time: " var i, t; var elm = document.getElementById("fast"); var length = myArray.length; for (i = 0; i < length; i++) { document.getElementById("access").innerHTML = "Value: " + i; } t = new Date().getTime() - t0; elm.innerHTML = fastText + t + "ms"; } </script> </body> </html>

有趣的是,最先执行的迭代似乎总是胜过另一个。但是,在每次执行几次之后,被认为是“坏代码”的东西似乎在大多数情况下都会获胜。也许比我更聪明的人可以解释原因。但现在,就语法而言,我坚持使用对我来说更易读的内容:
for (i = 0; i < arr.length; i++) {

如果

nodes
DOM nodeList

3
投票
jsperf

    
在我使用过的所有基准测试中,这始终是性能最好的。

for (i = 0, val; val = nodes[i]; i++) {
    doSomethingAwesome(val);
}


1
投票

我相信

nodes.length
 已经定义并且不会在每次使用时重新计算。因此第一个示例会更快,因为它定义了一个更少的变量。虽然差异是不明显的。

0
投票

© www.soinside.com 2019 - 2024. All rights reserved.