在 JavaScript 中循环数组的最快方法是什么?

IT技术 javascript performance loops
2021-01-13 02:57:27

我从书上知道你应该像这样写 for 循环:

for(var i=0, len=arr.length; i < len; i++){
    // blah blah
}

所以arr.length不会每次都计算。

其他人说编译器会对此做一些优化,所以你可以这样写:

for(var i=0; i < arr.length; i++){
    // blah blah
}

我只想知道实践中哪种方法最好?

6个回答

使用大多数现代浏览器执行此测试后:https : //jsben.ch/wY5fo

目前,最快的循环形式(在我看来也是最明显的句法)。

带有长度缓存的标准 for 循环

    var i = 0, len = myArray.length;
    while (i < len) {
        // your code
        i++
    }

我想说,这绝对是我为 JavaScript 引擎开发人员鼓掌的一个案例。运行时应该为了清晰而不是聪明而优化

我按照@BennettMcElwee 的建议使用前缀运算符进行了测试,它运行得更快一点: for(var i=0, len=myArray.length; i<len; ++i) 检查jsperf.com/caching-array-length/84
2021-03-17 02:57:27
有关使用的其他原因,请参阅优先使用前缀运算符而不是后缀++i
2021-03-31 02:57:27
有趣的是,在 IE9 中这更快: for (var i = 0, len = myArray.length; i < len; ++i) {} // 前缀 incr,而不是后缀
2021-04-02 02:57:27
你必须小心使用这个循环。我开始使用它并且由于我犯的一个错误而很难跟踪错误。如果像这样嵌套两个循环:jsfiddle.net/KQwmL/1您必须小心在两个循环中以不同的方式命名 var len,否则第二个循环将覆盖第一个 len。
2021-04-07 02:57:27
@WillshawMedia 您可以使用一条var语句声明多个变量它是如何写的,len实际上是按照你的建议进行的。
2021-04-11 02:57:27

循环遍历 javascript 数组的绝对最快方法是:

var len = arr.length;
while (len--) {
    // blah blah
}

有关完整比较,请参阅此帖子

我同意@jondavidjohn。我测试了这段代码,结果证明效率较低......检查 jsperf.com/caching-array-length/84
2021-03-12 02:57:27
我猜@jondavidjohn 你所说的“我在下面的答案”是“我在上面的答案”,哈哈。
2021-03-17 02:57:27
这个答案所基于的博客文章现在已经有将近 4 年的历史了,那时 js 引擎发生了很多变化,请参阅下面我的答案以获取更新的比较。
2021-03-24 02:57:27
不要忘记使用var(否则len成为全局变量)。此外,请参阅jsperf.com/loops以获取更多循环基准测试。
2021-03-27 02:57:27
上面的答案几乎普遍(跨浏览器)比 for 循环慢得多。请参阅已接受答案中的 JSPerf 链接。这是一个巨大的耻辱,因为它非常易读 IMO。
2021-03-31 02:57:27

截至 2016 年 6 月,在最新的 Chrome 中进行了一些测试(2016 年 5 月浏览器市场的 71%,并且还在增加):

  • 最快的循环是 for 循环,无论有没有缓存长度都提供非常相似的性能。(具有缓存长度的 for 循环有时比没有缓存的循环提供更好的结果,但差异几乎可以忽略不计,这意味着引擎可能已经进行了优化以支持标准并且可能是最直接的没有缓存的 for 循环)。
  • 带有递减的 while 循环比 for 循环慢大约 1.5 倍。
  • 使用回调函数(如标准 forEach)的循环比 for 循环慢大约 10 倍。

我认为这个线程太旧了,它误导程序员认为他们需要缓存长度,或者使用带有递减的反向遍历 whiles 来获得更好的性能,编写比简单直接的 for 循环更易读且更容易出错的代码。因此,我建议:

  • 如果您的应用程序迭代了很多项目,或者您的循环代码位于经常使用的函数中,那么简单的 for 循环就是答案:

    for (var i = 0; i < arr.length; i++) {
      // Do stuff with arr[i] or i
    }
    
  • 如果您的应用程序并没有真正迭代大量项目,或者您只需要在这里和那里进行小的迭代,那么使用标准的 forEach 回调或您选择的 JS 库中的任何类似函数可能更容易理解并且更不容易出错,因为index 变量作用域是关闭的,不需要使用方括号,直接访问数组值:

    arr.forEach(function(value, index) {
      // Do stuff with value or index
    });
    
  • 如果您确实需要在迭代数十亿行时花费几毫秒,并且数组的长度在整个过程中不会改变,则可以考虑在 for 循环中缓存长度。虽然我认为这在今天真的没有必要:

    for (var i = 0, len = arr.length; i < len; i++) {
      // Do stuff with arr[i]
    }
    
@Fr0sT 您的基准测试是一个不同的场景,遍历从索引 1 到 <= 长度的数组。当然,这会导致不同的结果。如果您尝试使用 < 长度遍历从零开始的数组——在我看来这是通常的场景——您会发现使用普通的“for”循环(缓存长度稍快)可以更好地优化结果。
2021-03-18 02:57:27
不。jsbench.github.io/#67b13d4e78cdd0d7a7346410d5becf12显示最快的是“反向循环,隐式比较,内联代码”(105,221 ops/sec),而“Loop, cached value, inlined code”得分仅为 76,635 ops31250sec )
2021-03-22 02:57:27
Kyopaxa 将基准更改为 (0 <= i < length),结果相同。“反向循环、隐式比较、函数调用”得分 365 kops/秒,而“循环、缓存值、内联代码”得分 350 kops/秒 (FF 51)
2021-03-24 02:57:27
@Fr0sT 如果您在没有相等比较的情况下更改从零开始的缓存 for 循环,例如for(let i=0, j=array.length; i < j; i++),则前向 for 循环会大大加快速度。在一些测试中,我运行它赢了,在大多数情况下它在误差范围内或反向循环内。
2021-03-24 02:57:27
@IsaacB 和所有人,对不起,我没有注意到工作台是非常不正确的 - 所有直接循环迭代 1..length,而反向循环迭代 length..0(arr[length] 项无效)。我修复了测试,现在它们显示以下结果:“循环,内联代码”360,616 次操作/秒 ±0.27%,“循环,缓存值,内联代码”345,786 次操作/秒 ±2.18%(原文如此!)“反向循环,隐式比较,内联代码” 322,640 ops/sec ±2.90% (!!!)。测试由 FF51 执行。新的工作台在这里jsbench.github.io/#6bdfcd2692ba80c16a68c88554281570因此,丑化循环似乎没有意义。
2021-03-29 02:57:27

现在是 2018 年,所以更新可能会很好......

我真的不得不不同意接受的答案它在不同的浏览器上有所延迟。有的做forEach快,有的for-loop,有的while 在这里是所有方法的基准http://jsben.ch/mW36e

arr.forEach( a => {
  // ...
}

并且因为您可以看到很多像这样的 for 循环,for(a = 0; ... )所以值得一提的是,如果没有 'var' 变量将被全局定义,这会极大地影响速度,因此它会变慢。

Duff 的设备在 opera 上运行得更快,但在 Firefox 中则不然

var arr = arr = new Array(11111111).fill(255);
var benches =     
[ [ "empty", () => {
  for(var a = 0, l = arr.length; a < l; a++);
}]
, ["for-loop", () => {
  for(var a = 0, l = arr.length; a < l; ++a)
    var b = arr[a] + 1;
}]
, ["for-loop++", () => {
  for(var a = 0, l = arr.length; a < l; a++)
    var b = arr[a] + 1;
}]
, ["for-loop - arr.length", () => {
  for(var a = 0; a < arr.length; ++a )
    var b = arr[a] + 1;
}]
, ["reverse for-loop", () => {
  for(var a = arr.length - 1; a >= 0; --a )
    var b = arr[a] + 1;
}]
,["while-loop", () => {
  var a = 0, l = arr.length;
  while( a < l ) {
    var b = arr[a] + 1;
    ++a;
  }
}]
, ["reverse-do-while-loop", () => {
  var a = arr.length - 1; // CAREFUL
  do {
    var b = arr[a] + 1;
  } while(a--);   
}]
, ["forEach", () => {
  arr.forEach( a => {
    var b = a + 1;
  });
}]
, ["for const..in (only 3.3%)", () => {
  var ar = arr.slice(0,arr.length/33);
  for( const a in ar ) {
    var b = a + 1;
  }
}]
, ["for let..in (only 3.3%)", () => {
  var ar = arr.slice(0,arr.length/33);
  for( let a in ar ) {
    var b = a + 1;
  }
}]
, ["for var..in (only 3.3%)", () => {
  var ar = arr.slice(0,arr.length/33);
  for( var a in ar ) {
    var b = a + 1;
  }
}]
, ["Duff's device", () => {
  var len = arr.length;
  var i, n = len % 8 - 1;

  if (n > 0) {
    do {
      var b = arr[len-n] + 1;
    } while (--n); // n must be greater than 0 here
  }
  n = (len * 0.125) ^ 0;
  if (n > 0) { 
    do {
      i = --n <<3;
      var b = arr[i] + 1;
      var c = arr[i+1] + 1;
      var d = arr[i+2] + 1;
      var e = arr[i+3] + 1;
      var f = arr[i+4] + 1;
      var g = arr[i+5] + 1;
      var h = arr[i+6] + 1;
      var k = arr[i+7] + 1;
    }
    while (n); // n must be greater than 0 here also
  }
}]];
function bench(title, f) {
  var t0 = performance.now();
  var res = f();
  return performance.now() - t0; // console.log(`${title} took ${t1-t0} msec`);
}
var globalVarTime = bench( "for-loop without 'var'", () => {
  // Here if you forget to put 'var' so variables'll be global
  for(a = 0, l = arr.length; a < l; ++a)
     var b = arr[a] + 1;
});
var times = benches.map( function(a) {
                      arr = new Array(11111111).fill(255);
                      return [a[0], bench(...a)]
                     }).sort( (a,b) => a[1]-b[1] );
var max = times[times.length-1][1];
times = times.map( a => {a[2] = (a[1]/max)*100; return a; } );
var template = (title, time, n) =>
  `<div>` +
    `<span>${title} &nbsp;</span>` +
    `<span style="width:${3+n/2}%">&nbsp;${Number(time.toFixed(3))}msec</span>` +
  `</div>`;

var strRes = times.map( t => template(...t) ).join("\n") + 
            `<br><br>for-loop without 'var' ${globalVarTime} msec.`;
var $container = document.getElementById("container");
$container.innerHTML = strRes;
body { color:#fff; background:#333; font-family:helvetica; }
body > div > div {  clear:both   }
body > div > div > span {
  float:left;
  width:43%;
  margin:3px 0;
  text-align:right;
}
body > div > div > span:nth-child(2) {
  text-align:left;
  background:darkorange;
  animation:showup .37s .111s;
  -webkit-animation:showup .37s .111s;
}
@keyframes showup { from { width:0; } }
@-webkit-keyframes showup { from { width:0; } }
<div id="container"> </div>

如果您考虑世界各地的所有用户,这绝对是正确的。但是,不幸的是,在世界的特定地区,IE8 还具有相关性。
2021-03-22 02:57:27
for..of 和 for..in 怎么样?
2021-03-28 02:57:27
@Maykonn 您可能想说“它适用于除 Opera Mini 以外的任何地方”
2021-04-02 02:57:27
如果可以的话,不仅不同的浏览器用不同的方法会有不同的结果,而且同样的浏览器在不同的输入下也会有不同的结果。一个巨大的 Number only Array 会被非常优化,而一个小的混合则不会。
2021-04-04 02:57:27
@Maykonn 默认视图中未列出,因为 0.18% 的用户拥有 IE8,您不应该浪费时间尝试支持它;2018年是一匹死马。
2021-04-09 02:57:27

如果顺序不重要,我更喜欢这种风格:

for(var i = array.length; i--; )

它缓存长度并且写入要短得多。但它会以相反的顺序遍历数组。

你刚刚杀了它。
2021-04-05 02:57:27
你不需要 i >= 0; 吗?
2021-04-07 02:57:27
@MarwaAhmad:否。i--返回一个数字,一旦数字0成为条件,则是false因为Boolean(0) === false.
2021-04-07 02:57:27