1. 程式人生 > 其它 >js 中你不知道的各種迴圈測速

js 中你不知道的各種迴圈測速

在前端js中,有著多種陣列迴圈的方式:

for 迴圈;
while 和 do-while 迴圈;
forEach、map、reduce、filter 迴圈;
for-of 迴圈;
for-in 迴圈;

那麼哪種迴圈的執行速度最快呢,我們今天來看一看。

在測試迴圈速度之前,我們先來建立一個有 100 萬資料的陣列:

const len = 100 * 10000;
const arr = [];
for (let i = 0; i < len; i++) {
  arr.push(Math.floor(Math.random() * len));
}

測試環境為:

電腦:iMac(10.13.6);
處理器:4.2 GHz Intel Core i7;
瀏覽器:Chrome(89.0.4389.82)

1. for 迴圈

for 迴圈是我們最常用的一種迴圈方式了,最大的好處就是結構清晰,能夠隨時 break 停止。

我們先用 10 次的測試來看下:

console.log('test for');
for (let k = 0; k < 10; k++) {
  console.time('for');
  let sum = 0;
  for (let i = 0; i < len; i++) {
    sum += arr[i] % 100;
  }
  console.timeEnd('for');
}

最終得到的結果:

在第 1 次和第 2 次時耗時比較多,從第 3 次開始就一直維持在 1.25ms 左右。

2. while 迴圈和 do-while 迴圈

這兩個放在一起,也是他們的結構足夠像,而且也能夠隨時 break 停止。

console.log('\ntest while');
for (let k = 0; k < 10; k++) {
  console.time('while');
  let sum = 0;
  let i = 0;
  while (i < len) {
    sum += arr[i] % 100;
    i++;
  }
  console.timeEnd('while');
}
console.log('\ntest do-while');
for (let k = 0; k < 10; k++) {
  console.time('do-while');
  let sum = 0;
  let i = 0;
  do {
    sum += arr[i] % 100;
    i++;
  } while (i < len);
  console.timeEnd('do-while');
}

while 迴圈和 do-while 迴圈的結果幾乎一樣,我們只看下 while 迴圈在瀏覽器上執行的結果:

跟 for 迴圈的速度不相上下。

3. forEach、map 和 reduce 迴圈

接下來來到我們常用的陣列三劍客了:forEach, map, reduce 等,這 3 個方法都是在 ES6 標準上新加的語法。

3.1 forEach 的簡要介紹

這幾種方法是無法停止迴圈的,無論使用break還是return,都無法停止整個迴圈。我們可以做一個測試,例如我想當遇到 3 的倍數時,即停止迴圈

[1, 2, 3, 4, 5].forEach((item) => {
  console.log(`before return: ${item}`);
  if (item % 3 === 0) {
    return;
  }
  console.log(`after return: ${item}`);
});

執行結果如下:

從執行的結果可以看到,我們的 return 只是沒有執行當時迴圈時後面的語句,但並沒有停止整個迴圈,後面的 4 和 5 依然正常輸出。

那迴圈是否真的像炫邁一樣停不下來嗎?並不,還有一種方式,可以停止迴圈。那就是丟擲異常:

try {
  [1, 2, 3, 4, 5].forEach((item) => {
    console.log(`before return: ${item}`);
    if (item % 3 === 0) {
      throw new Error('break forEach');
    }
    console.log(`after return: ${item}`);
  });
} catch (e) {}

在 forEach 中丟擲異常後,就可以停止該迴圈,然後再使用try-catch捕獲異常,避免整個服務被掛掉。

雖然可以停止 forEach 的迴圈,但實現起來麻煩了不少。因此若沒有停止整個迴圈的需求,可以使用 forEach, map 等迴圈方式;否則還是要使用其他的迴圈方式。

3.3 forEach 等的測速

好的,接下來我們就要測試這 3 個迴圈方式的迴圈速度了。

// forEach 的測試:
console.log('\ntest forEach');
for (let k = 0; k < 10; k++) {
  console.time('forEach');
  let sum = 0;
  arr.forEach((item) => {
    sum += item % 100;
  });
  console.timeEnd('forEach');
}
// map 的測試:
console.log('\ntest map');
for (let k = 0; k < 10; k++) {
  console.time('map');
  let sum = 0;
  arr.map((item) => {
    sum += item % 100;
  });
  console.timeEnd('map');
}
// reduce 的測試:
console.log('\ntest reduce');
for (let k = 0; k < 10; k++) {
  console.time('reduce');
  let sum = 0;
  arr.reduce((_, item) => {
    sum += item % 100;
  }, 0);
  console.timeEnd('reduce');
}

因這 3 個迴圈的時間差不多,我這裡就只截取了 forEach 的測試結果。

執行 10 次迴圈後,forEach 的執行時間差不多在 10.8ms 左右,比上面的 for 迴圈和 while 迴圈高了將近 10 倍的執行時間。

https://www.98891.com/article-63-1.html

4. for-of

ES6 借鑑 C++、Java、C# 和 Python 語言,引入了 for...of 迴圈,作為遍歷所有資料結構的統一的方法。

4.1 for-of 的簡要介紹

一個數據結構只要部署了 Symbol.iterator 屬性,就被視為具有 iterator 介面,就可以用 for...of 迴圈遍歷它的成員。也就是說,for...of 迴圈內部呼叫的是資料結構的 Symbol.iterator 方法。

for...of 迴圈可以使用的範圍包括陣列、Set 和 Map 結構、某些類似陣列的物件(比如 arguments 物件、DOM NodeList 物件)、後文的 Generator 物件,以及字串。

for-of 拿到的就是 value 本身,而 for-in 則拿到的是 key,然後通過 key 再獲取到當前資料。

const fruits = ['apple', 'banana', 'orange', 'lemon'];

for (const value of fruits) {
  console.log(value); // 'apple', 'banana', 'orange', 'lemon'
}

4.2 for-of 的迴圈測速

測試 for-of 迴圈速度的程式碼:

console.log('\ntest for-of');
for (let k = 0; k < 10; k++) {
  console.time('for-of');
  let sum = 0;
  for (const value of arr) {
    sum += value % 100;
  }
  console.timeEnd('for-of');
}

測試結果:

在多次重複同一個迴圈時,前 2 次的 for-of 迴圈時間會比較長,得在 15ms 以上。但後續的執行,迴圈速度就下降到 1.5ms 左右了,與 for 迴圈的時間差不多。

5. for-in 迴圈

for-in 通常用於 object 型別的迴圈,但也可以用來迴圈陣列,畢竟所有資料型別的祖先都是 object 型別。

console.log('\ntest for-in');
for (let k = 0; k < 10; k++) {
  console.time('for-in');
  let sum = 0;
  for (let key in arr) {
    sum += arr[key] % 100;
  }
  console.timeEnd('for-in');
}

測試結果:

for-in 迴圈的測速資料很驚人,簡直是獨一檔的存在了,最好的時候也至少需要 136ms 的時間。可見 for-in 的迴圈效率真的很低。

陣列型別的資料還是不要使用採用 for-in 迴圈了;Object 型別的可以通過Object.values()先獲取到所有的 value 資料,然後再使用 forEach 迴圈:

const obj = {};
for (let i = 0; i < len; i++) {
  obj[i] = Math.floor(Math.random() * len);
}
for (let k = 0; k < 10; k++) {
  console.time('forEach-values');
  let sum = 0;
  Object.values(obj).forEach((item) => {
    sum += item % 100;
  });
  console.timeEnd('forEach-values');
}

即使多了一步操作,迴圈時間也大概在 14ms 左右,要比 for-in 快很多。

6. 總結

我們把所有的迴圈資料放到一起對比一下,我們這裡將每個迴圈的測試次數調整為 100 次,橫軸是迴圈的次數,數軸是迴圈的時間:

for 迴圈、while 迴圈和 d-while 迴圈的時間最少;
for-of 迴圈的時間稍長;
forEach 迴圈、map 迴圈和 reduce 迴圈 3 者的資料差不多,但比 for-of 迴圈的時長更長一點;
for-in 迴圈所需要的時間最多;

每種迴圈的時長不一樣,我們在選擇迴圈方式時,除了考慮時間外,也要考慮到語義化和使用的場景。