JavaScript 控制流程 – 迴圈(for / while / do…while)
簡介
在程式設計中,迴圈(loop) 是最常見、也是最重要的控制流程之一。它讓我們可以把重複的工作自動化,不必手動撰寫大量相同的程式碼。對於 JavaScript 這類在瀏覽器與伺服器上都廣泛使用的語言而言,熟練掌握 for、while 與 do…while 三種基本迴圈,能讓你在資料處理、DOM 操作、非同步流程控制等各種情境下寫出簡潔、可讀且效能佳的程式。
本篇文章針對 初學者到中級開發者,從概念說明、實作範例、常見陷阱到最佳實踐,完整闡述 JavaScript 迴圈的使用方式。閱讀完後,你將能夠自行挑選最適合的迴圈型別,並避免在開發中踩到常見的坑。
核心概念
1. for 迴圈
最常見的迴圈形式,適合已知執行次數或需要同時操作索引與陣列元素的情況。
// 基本 for 迴圈:印出 1 到 5
for (let i = 1; i <= 5; i++) {
console.log(i);
}
- 初始化 (
let i = 1):只在迴圈開始時執行一次。 - 條件 (
i <= 5):每次迭代前都會檢查,若為false則結束迴圈。 - 遞增 (
i++):每次迭代結束後執行,通常用來改變索引值。
1.1. 迴圈與陣列
const fruits = ['🍎', '🍊', '🍇', '🍌'];
// 使用 for 直接取出陣列元素
for (let i = 0; i < fruits.length; i++) {
console.log(`第 ${i + 1} 個水果是 ${fruits[i]}`);
}
小技巧:
fruits.length放在條件式中會在每次迭代都重新計算長度,若陣列很大且長度不會變,建議先存到變數以提升效能。
const len = fruits.length;
for (let i = 0; i < len; i++) {
console.log(fruits[i]);
}
1.2. for…of:更直觀的寫法
ES6 引入的 for…of 讓我們直接遍歷可迭代物件(如陣列、字串、Set、Map),不需要手動管理索引。
for (const fruit of fruits) {
console.log(`水果:${fruit}`);
}
注意:
for…of無法直接取得索引,如需索引請搭配Array.entries():
for (const [index, fruit] of fruits.entries()) {
console.log(`第 ${index} 個水果是 ${fruit}`);
}
2. while 迴圈
適合執行次數不確定,但必須在每次迭代前先檢查條件的情況。
let count = 0;
while (count < 3) {
console.log(`第 ${count + 1} 次執行`);
count++; // 必須手動遞增,否則會變成無限迴圈
}
2.1. 讀取使用者輸入(模擬)
let input;
do {
input = prompt('請輸入一個正整數(輸入 0 結束):');
const num = Number(input);
if (num > 0) console.log(`你輸入了 ${num}`);
} while (Number(input) !== 0);
上例雖使用 do…while(稍後說明),但展示了 在條件不成立前仍必須執行一次 的情境。
3. do…while 迴圈
與 while 的差別在於先執行一次再判斷條件,適合「至少要跑一次」的需求。
let i = 0;
do {
console.log(`執行第 ${i + 1} 次`);
i++;
} while (i < 3);
3.1. 例子:簡易的密碼驗證
let attempts = 0;
let password;
do {
password = prompt('請輸入密碼(限 3 次):');
attempts++;
if (password === 'letmein') {
alert('登入成功!');
break; // 正確即跳出迴圈
}
alert('密碼錯誤');
} while (attempts < 3);
if (attempts === 3 && password !== 'letmein') {
alert('已超過嘗試次數');
}
4. 迴圈的控制關鍵字
| 關鍵字 | 說明 |
|---|---|
break |
立即跳出整個迴圈,常用於找到目標後停止搜尋。 |
continue |
跳過本次迭代的剩餘程式,直接進入下一次迴圈判斷。 |
label + break/continue |
針對多層迴圈指定跳出/繼續的目標(較少使用,需小心維護)。 |
// 例:找出第一個大於 10 的偶數
const numbers = [3, 7, 12, 14, 9];
let result = null;
for (const n of numbers) {
if (n % 2 !== 0) continue; // 只關心偶數
if (n > 10) {
result = n;
break; // 找到即停止
}
}
console.log(result); // 12
常見陷阱與最佳實踐
1. 無限迴圈
最常見的錯誤是遺忘在迴圈內改變條件變數,導致程式永遠卡住。
let i = 0;
while (i < 5) {
console.log(i);
// i++ 被遺漏 → 無限迴圈
}
解法:在撰寫迴圈時,先在紙上寫下「初始化 → 條件 → 變化」三步,確保每一步都有對應程式碼。
2. for 迴圈的變數作用域
使用 var 宣告的迴圈變數會提升(hoist)且作用域是整個函式,容易產生意外。
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 會印出 3,3,3
}
改用 let(塊級作用域)即可解決:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 0,1,2
}
3. 不要在條件式中執行副作用
for (let i = 0; i < arr.push(1); i++) { /* ... */ }
arr.push(1) 會在每次判斷時改變陣列,造成預期外的結果。條件式應保持純粹(只做比較)。
4. 使用 for…in 迭代陣列時的陷阱
for…in 會遍歷所有可列舉屬性(包括原型鏈),不適合陣列。
Array.prototype.foo = 'bar';
const a = [1, 2, 3];
for (const i in a) {
console.log(i); // 0,1,2,foo ← 不想要的
}
遍歷陣列請使用 for…of、for 或 Array.prototype.forEach。
5. 最佳實踐
| 實踐 | 說明 |
|---|---|
使用 let/const 取代 var |
防止變數提升與全域污染。 |
| 先寫出「退出條件」再寫遞增/遞減 | 可減少忘記更新變數的機會。 |
當迭代次數已知,優先使用 for |
可直接取得索引,效能上略優。 |
需要「至少一次」的執行時,用 do…while |
讓程式意圖更清晰。 |
| 大量資料處理時,避免在迴圈內建立新物件 | 減少 GC 壓力,提升效能。 |
使用 Array 的高階方法(map、filter、reduce) |
可讓程式更具宣告式、可讀性更好。 |
實際應用場景
1. 表格資料渲染(DOM)
const users = [
{ name: 'Alice', age: 28 },
{ name: 'Bob', age: 34 },
{ name: 'Carol', age: 22 }
];
const tbody = document.querySelector('#userTable tbody');
for (const user of users) {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${user.name}</td><td>${user.age}</td>`;
tbody.appendChild(tr);
}
2. 分頁載入(API)
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`/api/products?page=${page}`);
const { data, nextPage } = await response.json();
renderProducts(data); // 自訂函式
if (nextPage) {
page = nextPage;
} else {
hasMore = false;
}
}
3. 文字遊戲:猜數字
const target = Math.floor(Math.random() * 100) + 1;
let guess;
let attempts = 0;
do {
guess = Number(prompt('猜一個 1~100 的數字:'));
attempts++;
if (guess > target) alert('太大了!');
else if (guess < target) alert('太小了!');
else alert(`恭喜!你用了 ${attempts} 次猜中`);
} while (guess !== target);
4. 大量資料的批次處理(Node.js)
const fs = require('fs');
const lines = fs.readFileSync('bigfile.txt', 'utf8').split('\n');
let batch = [];
const BATCH_SIZE = 500;
for (let i = 0; i < lines.length; i++) {
batch.push(lines[i]);
if (batch.length === BATCH_SIZE || i === lines.length - 1) {
// 假設 processBatch 為非同步 I/O
await processBatch(batch);
batch = []; // 清空以便下一批
}
}
總結
for、while、do…while是 JavaScript 中最基本的迴圈結構,各有適用情境:已知次數 →for、條件先判斷 →while、至少執行一次 →do…while。for…of與for…in是 ES6 加入的語法,前者適合遍歷可迭代物件,後者僅用於遍歷物件屬性。- 常見陷阱包括無限迴圈、變數提升、條件副作用等,透過使用
let/const、明確的退出條件與避免在條件式中執行副作用,可大幅降低錯誤率。 - 在實務開發中,迴圈常被用於資料渲染、API 分頁、遊戲互動、批次處理等多種場景。適當選擇迴圈類型與最佳實踐,能讓程式碼更具可讀性、效能與可維護性。
掌握了迴圈的核心概念與實作技巧後,你將能在任何 JavaScript 專案中自信地處理重複性工作,並寫出更乾淨、可靠的程式。祝你寫程式愉快! 🎉