[JS] for、for...in、forEach、for...of 的差別

JavaScript 的 for 迴圈有好幾種用法,這次來研究其中的差異。


範例

本篇文章的範例程式碼都會以下列的物件、陣列為範例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 普通陣列
const arr = [1, 2, 3];

// 帶有空值的陣列
const arrWithEmpty = [1, , 3];

// 帶有物件屬性的陣列(陣列本身還是物件,所以具有物件的特性)
const arrWithAttr = [1, 2, 3];
arrWithAttr.a = 'a';

// 物件
const obj = {
a: 1,
b: 2,
c: 3,
};

普通陣列

  1. for

    • for 是最基本的迴圈寫法,透過陣列的索引(index)取值

      1
      2
      3
      4
      5
      6
      7
      for (let i = 0; i < arr.length; ++i) {
      console.log(arr[i]);
      }
      // output:
      // 1
      // 2
      // 3
    • for 可以自行設定迴圈執行的區間,也可以用 break 來中斷。

      1
      2
      3
      4
      5
      6
      7
      8
      for (let i = 1; i < arr.length - 1; i += 1) {
      console.log(arr[i]);
      if (arr[i] > 1) {
      break;
      }
      }
      // output:
      // 2
  2. for…in

    • for…in 與 for 都是以索引值來造訪陣列。

    • for…in 可以用 break 來中斷,但是不能設定開始與結束的索引值。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      for (let i in arr) {
      console.log(arr[i]);
      if (arr[i] > 1) {
      break;
      }
      }
      // output:
      // 1
      // 2
  3. forEach

    • forEach 是陣列方法,預設的第一個參數會取得陣列的值。

    • forEach 不能被中斷、也不能設定開始與結束的索引值。

    • 類陣列(Array-like)不一定有 forEach 的方法(NodeList 有、Arguments 沒有)。

      1
      2
      3
      4
      5
      6
      7
      arr.forEach((item) => {
      console.log(item);
      });
      // output:
      // 1
      // 2
      // 3
    • forEach 可以選擇性的加上第 2、3 個參數來取得索引值和原始陣列。

      1
      2
      3
      4
      5
      6
      7
      arr.forEach((item, index, array) => {
      console.log(`${item}, ${index}, ${array}`);
      });
      // output:
      // 1, 0, 1,2,3
      // 2, 1, 1,2,3
      // 3, 2, 1,2,3
  4. for…of

    • for…of 直接取得陣列的值,沒有索引值。

    • for…of 可以用 break 來中斷,但是不能設定開始與結束的索引值。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      for (const item of arr) {
      console.log(item);
      if (item > 1) {
      break;
      }
      }
      // output:
      // 1
      // 2
    • 使用 for…of 取得索引值需要先使用 entries 方法產生迭代器物件(iterator)。

      1
      2
      3
      4
      5
      6
      7
      for (const [index, item] of arr.entries()) {
      console.log(`${index}, ${item}`);
      }
      // output:
      // 0, 1
      // 1, 2
      // 2, 3

帶有空值的陣列

  • for 遇到空值會回傳 undefined。

  • for…in 遇到空值會跳過。

  • forEach 遇到空值會跳過。

  • for…of 遇到空值會回傳 undefined。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    for (let i = 0; i < arrWithEmpty.length; i++) {
    console.log(arrWithEmpty[i]);
    }
    // output:
    // 1
    // undefined
    // 3

    for (let i in arrWithEmpty) {
    console.log(arrWithEmpty[i]);
    }
    // output:
    // 1
    // 3

    arrWithEmpty.forEach((item) => {
    console.log(item);
    });
    // output:
    // 1
    // 3

    for (const item of arrWithEmpty) {
    console.log(item);
    }
    // output:
    // 1
    // undefined
    // 3

帶有物件屬性的陣列

  • for 不會取得屬性的值

  • for...in 會取得屬性的值

  • forEach 不會取得屬性的值

  • for…of 不會取得屬性的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    for (let i = 0; i < arrWithAttr.length; i++) {
    console.log(arrWithAttr[i]);
    }
    // output:
    // 1
    // 2
    // 3

    for (let i in arrWithAttr) {
    console.log(arrWithAttr[i]);
    }
    // output:
    // 1
    // 2
    // 3
    // a

    arrWithAttr.forEach((item) => {
    console.log(item);
    });
    // output:
    // 1
    // 2
    // 3

    for (const item of arrWithAttr) {
    console.log(item);
    }
    // output:
    // 1
    // 2
    // 3

物件

  • for 無法直接操作物件,需要先產生迭代器物件(iterator)。

  • for...in 可以取得物件的 key

  • forEach 無法直接操作物件,需要先產生迭代器物件(iterator)。

  • for…of 無法直接操作物件,需要先產生迭代器物件(iterator)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    const objKeys = Object.keys(obj);
    for (let i = 0; i < objKeys.length; i += 1) {
    console.log(obj[objKeys[i]]);
    }
    // output:
    // 1
    // 2
    // 3

    for (let i in obj) {
    console.log(i);
    }
    // output:
    // a
    // b
    // c

    Object.keys(obj).forEach((item) => {
    console.log(obj[item]);
    });
    // output:
    // 1
    // 2
    // 3

    for (const item of Object.values(obj)) {
    console.log(item);
    }
    // output:
    // 1
    // 2
    // 3

其他補充

  1. 使用 for、for…in、for…of 時,請勿使用 var 來宣告裡面的變數(早期的文章都會使用 var,因為那個時候只有 var),請使用 let 或 const 來避免污染全域環境。
  2. Vue 的 v-for 撰寫範例通常都是 v-for="item in items,但 官方文件 裡面有寫到可以使用 v-for="item of items 來撰寫,這會更貼近 JavaScript 迭代的語法。
  3. forEach 方法會傳入一個 callback function,如果使用箭頭函式會有 this 指向的問題需要注意。
  4. forEach 不適合搭配非同步使用,forEach 不會等待非同步完成才進入下一個循環,會以同步的方式執行所有內容,使用 for、for…in、for…of 搭配 async、await 較佳。

參考文章: