本文 AI 產出,尚未審核

JavaScript – 控制流程(Control Flow)

主題:巢狀迴圈(Nested Loops)


簡介

在程式設計中,迴圈是讓程式重複執行相同或類似程式碼的核心工具。當我們需要同時處理兩層(甚至多層)資料結構時,巢狀迴圈 就成為不可或缺的技巧。常見的例子包括二維陣列、棋盤遊戲、表格資料的遍歷,以及圖形繪製等情境。掌握巢狀迴圈的寫法與注意事項,能讓你的程式碼既簡潔高效,避免因為錯誤的迴圈控制而產生無窮迴圈或效能瓶頸。

本篇文章將從基本概念出發,逐步帶你了解如何在 JavaScript 中正確使用巢狀迴圈,並提供實務範例、常見陷阱與最佳實踐,協助你在日常開發中快速上手。


核心概念

1. 為什麼需要巢狀迴圈?

  • 二維或多維資料:如 matrix[行][列]、JSON 中的多層陣列。
  • 組合問題:產生所有可能的配對或排列(例如卡牌組合、座位排列)。
  • 圖形與網格:在 Canvas 或 DOM 中繪製格子、表格、棋盤等。

小技巧:在開始寫巢狀迴圈前,先確認資料結構的層級與大小,避免寫出不必要的迴圈層數。

2. 基本語法

for (let i = 0; i < outerLength; i++) {
    // 外層迴圈的程式碼
    for (let j = 0; j < innerLength; j++) {
        // 內層迴圈的程式碼
    }
}
  • ij 為慣例的迴圈變數,分別代表外層與內層的索引。
  • 注意:內層迴圈的執行次數會乘以外層迴圈的次數,總時間複雜度為 O(outerLength × innerLength)

3. 巢狀迴圈的變形

變形 說明 範例
while + for 內層使用 while,外層使用 for,適合需要動態判斷內層結束條件的情況 參考範例 4
for...of 使用 for...of 直接遍歷陣列元素,語意更直觀 參考範例 5
break / continue 在內層或外層使用 breakcontinue 控制流程 參考範例 3

程式碼範例

範例 1:遍歷二維陣列(矩陣)並計算總和

// 假設有一個 3x3 的矩陣
const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

let sum = 0;

// 外層遍歷每一列
for (let row = 0; row < matrix.length; row++) {
  // 內層遍歷列中的每一個元素
  for (let col = 0; col < matrix[row].length; col++) {
    sum += matrix[row][col]; // 累加
  }
}

console.log('矩陣總和 =', sum); // 45

說明matrix.length 取得列數,matrix[row].length 取得每列的欄位數。此寫法適用於不規則矩陣(每列長度不同)也能正確運作。


範例 2:產生九九乘法表(2‑dimensional table)

for (let i = 1; i <= 9; i++) {
  let line = '';
  for (let j = 1; j <= 9; j++) {
    line += `${i}×${j}=${i * j}`.padEnd(8, ' ');
  }
  console.log(line);
}
  • 外層 i 代表「乘數」,內層 j 代表「被乘數」。
  • 使用 padEnd 讓每個結果對齊,輸出更易讀。

範例 3:使用 break 退出內層迴圈(搜尋座標)

const grid = [
  ['🟩', '🟦', '🟨'],
  ['🟨', '🟩', '🟥'],
  ['🟦', '🟨', '🟩']
];

let target = '🟥';
let position = null;

for (let y = 0; y < grid.length; y++) {
  for (let x = 0; x < grid[y].length; x++) {
    if (grid[y][x] === target) {
      position = { x, y };
      break; // 找到目標後直接退出內層
    }
  }
  if (position) break; // 也退出外層
}

console.log('目標座標:', position); // { x: 2, y: 1 }
  • 重點:在內層找到目標後,用 break 先退出內層,再檢查外層變數 position 是否已設定,若已設定則再次 break 離開外層。這樣可以避免不必要的迭代

範例 4:while + for 結合(動態內層長度)

let i = 0;
while (i < 5) {               // 外層使用 while,條件可變
  const innerCount = Math.floor(Math.random() * 4) + 1; // 1~4 次
  console.log(`第 ${i + 1} 次外層迴圈,內層執行 ${innerCount} 次`);
  for (let j = 0; j < innerCount; j++) {
    console.log(`  內層第 ${j + 1} 次`);
  }
  i++;
}
  • 內層次數 隨機,展示了在外層條件不固定時,仍能安全使用巢狀迴圈的方式。

範例 5:使用 for...of 遍歷陣列的陣列(更直觀)

const seats = [
  ['A1', 'A2', 'A3'],
  ['B1', 'B2', 'B3'],
  ['C1', 'C2', 'C3']
];

for (const row of seats) {          // 直接取得每一列(陣列)
  for (const seat of row) {        // 再直接取得每個座位
    console.log('座位編號:', seat);
  }
}
  • for...of 省去索引變數,讓程式碼更易讀,特別適合只需要元素本身的情境。

常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記更新內層變數 內層迴圈的計數器若未正確遞增,會導致無窮迴圈。 確認 for 的第三個表達式(i++j++)正確。
外層與內層變數名稱衝突 使用相同變數名(如兩層都用 i)會互相覆蓋。 為不同層級使用不同名稱(ijk)或使用 let/const 限定作用域。
過度巢狀 超過三層巢狀會讓程式難以閱讀且效能下降。 考慮使用 函式抽取陣列方法mapreduce)或 資料結構變換
忽略資料不規則性 二維陣列的每列長度不一致時,直接使用固定長度會出錯。 在內層迴圈使用 array[row].length 動態取得長度。
未使用 break/continue 控制流程 需要提前結束搜尋時仍遍歷完整集合,浪費效能。 依需求加入 break(退出)或 continue(跳過)以減少不必要的迭代。

最佳實踐

  1. 明確命名:外層使用 rowi,內層使用 colj,讓閱讀者一眼看出索引層級。
  2. 限制巢層深度:若超過兩層,先思考是否能拆成 函式陣列方法,提升可維護性。
  3. 使用 const / let:確保變數作用域僅限於迴圈本身,避免意外污染全域。
  4. 提前退出:在搜尋類問題中,一旦找到目標立即 break,減少不必要的運算。
  5. 效能測試:對於大量資料(如千萬筆)時,使用 時間測試console.time)確認巢狀迴圈是否為瓶頸,必要時改用 分治平行處理(Web Workers)。

實際應用場景

  1. 表格資料渲染
    前端框架(React、Vue)常會把資料陣列映射成 <tr><td>,內層迴圈負責產生每一格的內容。

  2. 遊戲棋盤
    如井字棋、五子棋、掃雷等,都以二維陣列儲存棋子或格子狀態,巢狀迴圈用於檢查勝負更新畫面

  3. 圖像處理
    在 Canvas 中操作像素時,需要遍歷 width × height 的像素矩陣,典型的雙層迴圈配合 ImageData 進行顏色調整。

  4. 統計分析
    例如計算學生成績的平均分、最高分、最低分,資料通常以「班級 × 學生」的二維結構存放,巢狀迴圈能快速聚合資訊。

  5. 產生所有組合
    在電商平台需要產生「顏色 × 尺寸」的商品變體列表,巢狀迴圈能一次產出所有可能的 SKU。


總結

  • 巢狀迴圈 是處理二維或多維資料、組合問題與圖形繪製的基礎工具。
  • 正確掌握 外層/內層變數的命名、迭代條件與退出控制,能避免常見的無窮迴圈與效能問題。
  • 最佳實踐 包含:限制巢層深度、使用 break/continue 提前退出、利用 for...of 提升可讀性、在需要時抽離函式或改用陣列方法。
  • 在實務開發中,巢狀迴圈常出現在 表格渲染、棋盤遊戲、圖像處理、統計分析與商品組合產生 等情境,熟練後能大幅提升開發效率與程式碼品質。

透過本篇的概念說明與範例示範,你應該已經能在自己的 JavaScript 專案中自信地使用巢狀迴圈,寫出既正確易於維護的程式碼。祝你寫程式愉快,持續探索更多控制流程的奧妙!