本文 AI 產出,尚未審核

JavaScript 控制流程:深入了解 for...infor...of


簡介

在 JavaScript 中,**迴圈(loop)**是處理集合資料、重複執行相同程式碼的核心工具。除了最常見的 forwhile,ES6 之後引入的 for...infor...of 兩種語法,讓我們在遍歷物件與可迭代資料(iterable)時更加直觀且安全。

  • for...in 主要用於 列舉(enumerate)物件的屬性名稱,適合處理純粹的 key‑value 結構,如普通物件或 Map 的鍵集合。
  • for...of 則是 遍歷可迭代物件的值(value),支援陣列、字串、SetMaparguments 甚至自訂的 iterable。

了解這兩者的差異與正確使用時機,能讓程式碼更具可讀性、效能更佳,同時避免常見的錯誤陷阱。以下,我們將從概念說明、實作範例、最佳實踐一路帶你深入掌握。


核心概念

1. for...in:列舉物件屬性名稱

for...in 會遍歷 物件本身以及原型鏈上 可列舉(enumerable)的屬性名稱(key),回傳的是字串形式的屬性名。

const person = {
  name: 'Alice',
  age: 28,
  hobby: 'photography'
};

for (const key in person) {
  console.log(key);           // => "name", "age", "hobby"
}

為什麼會遍歷原型鏈?

for...in 的設計初衷是 列舉所有可見屬性,包括從原型繼承的屬性。若只想遍歷「自有屬性」(own properties),必須搭配 hasOwnProperty 檢查:

for (const key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key, person[key]);
  }
}

小技巧:在 ES5+ 中,Object.keys()Object.values()Object.entries() 都可以直接取得自有可列舉屬性,往往比 for...in 更安全。


2. for...of:遍歷可迭代物件的值

for...of 只關心 可迭代(iterable) 的資料結構,透過內建的 @@iteratorSymbol.iterator)取得每一次的值。它不會遍歷原型鏈,也不會返回索引或鍵。

const colors = ['red', 'green', 'blue'];

for (const color of colors) {
  console.log(color);   // => "red", "green", "blue"
}

支援的資料類型

類型 說明
Array 常見的陣列遍歷
String 逐字元遍歷(包括 Unicode 代理對)
Set 只會返回唯一值
Map 依序返回 [key, value] 兩元組
arguments 函式內的類陣列物件
自訂 iterable 必須實作 Symbol.iterator 方法
// 文字逐字元遍歷(支援 emoji)
const emoji = '👍🏽💖';
for (const ch of emoji) {
  console.log(ch);   // => "👍🏽", "💖"
}

3. for...in vs for...of:何時該選哪一個?

條件 使用 for...in 使用 for...of
想取得 屬性名稱(key)
只遍歷 自有屬性(不含原型) ❌(需 hasOwnProperty ✅(適用於 iterable)
需要 值的順序(如陣列索引)
處理 字串、Set、Map
需要 兼容舊瀏覽器(ES5 前) ❌(需 ES6)

程式碼範例

以下提供 5 個實用範例,示範在不同情境下如何正確使用 for...infor...of

範例 1:列舉物件屬性並過濾自有屬性

const car = {
  brand: 'Toyota',
  model: 'Corolla',
  year: 2020
};

Object.prototype.wheels = 4; // 假設有人在原型上加了屬性

for (const prop in car) {
  // 只處理自有屬性
  if (car.hasOwnProperty(prop)) {
    console.log(`${prop}: ${car[prop]}`);
  }
}
// 輸出:
// brand: Toyota
// model: Corolla
// year: 2020

註解hasOwnProperty 可避免遍歷到 wheels 這類原型屬性。


範例 2:使用 Object.entries() 搭配 for...of 直接取得鍵值對

const settings = {
  theme: 'dark',
  language: 'zh-TW',
  notifications: true
};

for (const [key, value] of Object.entries(settings)) {
  console.log(`${key} => ${value}`);
}
// 輸出:
// theme => dark
// language => zh-TW
// notifications => true

說明Object.entries() 會回傳一個陣列,裡面的每個子陣列都是 [key, value],配合 for...of 可一次取得兩者。


範例 3:遍歷 Map 並同時取得鍵和值

const scores = new Map([
  ['Alice', 95],
  ['Bob', 82],
  ['Charlie', 78]
]);

for (const [name, score] of scores) {
  console.log(`${name} 的成績是 ${score}`);
}
// 輸出:
// Alice 的成績是 95
// Bob 的成績是 82
// Charlie 的成績是 78

重點Map 本身即為 iterable,for...of 直接返回 [key, value] 陣列。


範例 4:使用 for...of 逐字元處理 Unicode 字串

const text = 'Hello 🌍!';

for (const ch of text) {
  console.log(ch);
}
// 輸出:
// H e l l o   🌍 !
// 每個字元(包括 emoji)都正確被切分

為什麼 for...in 不適合?
for...in 會遍歷字串的索引(如 "0", "1", ...),而且無法正確處理代理對(surrogate pair)產生的 emoji。


範例 5:自訂 Iterable 物件(實作 Symbol.iterator

class Counter {
  constructor(limit) {
    this.limit = limit;
  }

  // 必須回傳一個 iterator 物件
  [Symbol.iterator]() {
    let count = 0;
    const limit = this.limit;

    // iterator 必須有 next() 方法,回傳 { value, done }
    return {
      next() {
        if (count < limit) {
          return { value: count++, done: false };
        }
        return { done: true };
      }
    };
  }
}

const counter = new Counter(5);
for (const num of counter) {
  console.log(num); // 0 1 2 3 4
}

說明:只要物件實作 Symbol.iterator,就能使用 for...of 迭代,這讓自訂資料結構也能享有同樣的語法便利。


常見陷阱與最佳實踐

陷阱 說明 解決方案
遍歷陣列時使用 for...in for...in 會列舉索引(字串)且會遍歷原型鏈,導致順序不一定,且效能較差。 永遠使用 for...of 或傳統 forArray.prototype.forEach
忘記 hasOwnProperty 直接使用 for...in 會把原型上的屬性也列出,可能導致意外行為。 加上 if (obj.hasOwnProperty(key)),或改用 Object.keys()Object.entries()
for...of 中修改陣列長度 迭代過程中若增減元素,可能導致跳過或重複遍歷。 避免在迭代期間改變原始陣列,或先複製 (slice()) 再遍歷。
使用 for...of 迭代非 iterable 如普通物件、nullundefined 會拋出 TypeError 先檢查 obj != null && typeof obj[Symbol.iterator] === 'function',或使用 Object.entries()
忘記 break / continue for...offor...in 都支援 breakcontinue,但在 for...of 中使用 return 只能跳出整個函式。 清楚瞭解控制流程,必要時使用標籤(label)或抽離迴圈的函式。

最佳實踐

  1. 優先使用 for...of:遍歷陣列、字串、Set、Map 時語意最清晰。
  2. 僅在需要屬性名稱時使用 for...in,且配合 hasOwnPropertyObject.keys()
  3. 避免在迴圈內改變被迭代的資料結構,若必須變更,先複製再處理。
  4. 使用 const 宣告迭代變數,確保迴圈內不會意外重新指派。
  5. 在大型迭代時考慮效能for...of 的底層實作相較於傳統 for 仍有微小差距,對於極限效能需求可自行測試。

實際應用場景

1. 表格資料渲染(陣列 → DOM)

const rows = [
  { id: 1, name: 'Alice', score: 92 },
  { id: 2, name: 'Bob',   score: 85 },
  { id: 3, name: 'Carol', score: 78 }
];

const tbody = document.querySelector('#result tbody');

for (const row of rows) {
  const tr = document.createElement('tr');
  tr.innerHTML = `
    <td>${row.id}</td>
    <td>${row.name}</td>
    <td>${row.score}</td>
  `;
  tbody.appendChild(tr);
}

使用 for...of 可直接取得每筆資料,寫出簡潔且易讀的渲染程式碼。

2. 解析 API 回傳的 JSON(物件屬性列舉)

async function fetchUserSettings() {
  const resp = await fetch('/api/settings');
  const data = await resp.json(); // 假設回傳物件

  for (const key in data) {
    if (data.hasOwnProperty(key)) {
      console.log(`設定 ${key} = ${data[key]}`);
    }
  }
}

若不確定回傳的屬性是否為自有屬性,使用 hasOwnProperty 保障安全。

3. 計算唯一字元出現次數(使用 Set + for...of

function countUniqueChars(str) {
  const unique = new Set();
  for (const ch of str) {
    unique.add(ch);
  }
  return unique.size;
}

console.log(countUniqueChars('abcaab')); // 3

Set 天然去除重複,搭配 for...of 能簡潔完成任務。

4. 自訂資料結構的遍歷(例如樹狀結構)

class TreeNode {
  constructor(value, children = []) {
    this.value = value;
    this.children = children;
  }

  *[Symbol.iterator]() {
    yield this.value;
    for (const child of this.children) {
      yield* child; // 深度優先遍歷
    }
  }
}

const tree = new TreeNode(1, [
  new TreeNode(2, [new TreeNode(4), new TreeNode(5)]),
  new TreeNode(3)
]);

for (const val of tree) {
  console.log(val); // 1 2 4 5 3
}

透過自訂 Symbol.iterator,任何樹狀資料都能以 for...of 直觀遍歷。


總結

  • for...in 列舉物件的屬性名稱,適合需要鍵名的情境,但必須小心原型鏈與非自有屬性的問題。
  • for...of 遍歷可迭代資料的值,支援陣列、字串、Set、Map 以及自訂 iterable,語意最為直觀且不會受到原型鏈干擾。
  • 在開發中,優先考慮 for...of,除非真的需要鍵名或要處理舊版瀏覽器環境時才使用 for...in
  • 配合 Object.keys()Object.entries()hasOwnProperty 等輔助方法,可讓 for...in 的使用更安全。
  • 了解兩者的差異與限制,能寫出 更易讀、效能更佳且不易出錯 的程式碼,無論是前端 UI 渲染、資料處理或是自訂資料結構,都能從容應對。

祝你在 JavaScript 的控制流程世界裡,玩得開心、寫得順手! 🎉