JavaScript 基礎:迴圈的完整指南
各位好!
這篇是 JavaScript 系列的第七單元,我們要學習迴圈(Loop)。迴圈讓我們可以重複執行程式碼,處理大量資料時特別有用。
為什麼需要迴圈?
假設你要印出 1 到 100 的數字:
// 沒有迴圈的話...
console.log(1);
console.log(2);
console.log(3);
// ... 要寫 100 行
// 使用迴圈
for (let i = 1; i <= 100; i++) {
console.log(i);
}
for 迴圈
最常用的迴圈,適合已知重複次數的情況。
基本語法
// 語法:for (初始化; 條件; 遞增) { 執行內容 }
for (let i = 0; i < 5; i++) {
console.log(i);
}
// 輸出:0, 1, 2, 3, 4
// 執行流程:
// 1. 初始化 i = 0
// 2. 檢查條件 i < 5(true),執行內容
// 3. 遞增 i++(i 變成 1)
// 4. 檢查條件 i < 5(true),執行內容
// 5. 重複直到條件為 false
常見用法
// 倒數
for (let i = 5; i > 0; i--) {
console.log(i);
}
// 輸出:5, 4, 3, 2, 1
// 每次增加 2
for (let i = 0; i < 10; i += 2) {
console.log(i);
}
// 輸出:0, 2, 4, 6, 8
// 遍歷陣列
const fruits = ["蘋果", "香蕉", "橘子"];
for (let i = 0; i < fruits.length; i++) {
console.log(`${i + 1}. ${fruits[i]}`);
}
// 輸出:
// 1. 蘋果
// 2. 香蕉
// 3. 橘子
// 巢狀迴圈(九九乘法表)
for (let i = 1; i <= 9; i++) {
for (let j = 1; j <= 9; j++) {
console.log(`${i} x ${j} = ${i * j}`);
}
}
while 迴圈
當不確定要重複幾次,只知道終止條件時使用。
// 基本語法
let count = 0;
while (count < 5) {
console.log(count);
count++;
}
// 輸出:0, 1, 2, 3, 4
// 實用範例:密碼驗證
let password = "";
let attempts = 0;
const correctPassword = "1234";
while (password !== correctPassword && attempts < 3) {
// 這裡通常會用 prompt() 讓使用者輸入
password = "1234"; // 模擬輸入
attempts++;
}
if (password === correctPassword) {
console.log("登入成功");
} else {
console.log("嘗試次數過多");
}
// 無窮迴圈(小心使用)
// while (true) {
// // 除非有 break,否則會永遠執行
// }
do...while 迴圈
至少會執行一次,然後才檢查條件。
// 基本語法
let i = 0;
do {
console.log(i);
i++;
} while (i < 5);
// 輸出:0, 1, 2, 3, 4
// 與 while 的差異
let x = 10;
while (x < 5) {
console.log("這行不會執行"); // 條件不符,不執行
}
do {
console.log("這行會執行一次"); // 至少執行一次
} while (x < 5);
// 實用範例:選單系統
let choice;
do {
console.log("1. 選項一");
console.log("2. 選項二");
console.log("0. 離開");
// choice = prompt("請選擇:");
choice = "0"; // 模擬輸入
} while (choice !== "0");
for...of 迴圈
遍歷可迭代物件(陣列、字串等)的值。
// 遍歷陣列
const fruits = ["蘋果", "香蕉", "橘子"];
for (const fruit of fruits) {
console.log(fruit);
}
// 輸出:蘋果、香蕉、橘子
// 遍歷字串
const str = "Hello";
for (const char of str) {
console.log(char);
}
// 輸出:H、e、l、l、o
// 與傳統 for 迴圈比較
// 傳統方式
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
// for...of 方式(更簡潔)
for (const fruit of fruits) {
console.log(fruit);
}
// 使用解構
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 }
];
for (const { name, age } of users) {
console.log(`${name} is ${age} years old`);
}
// 使用 entries() 取得索引
for (const [index, fruit] of fruits.entries()) {
console.log(`${index}: ${fruit}`);
}
for...in 迴圈
遍歷物件的可列舉屬性。
// 遍歷物件
const user = {
name: "Charlie",
age: 28,
city: "Taipei"
};
for (const key in user) {
console.log(`${key}: ${user[key]}`);
}
// 輸出:
// name: Charlie
// age: 28
// city: Taipei
// 遍歷陣列(不推薦)
const arr = ["a", "b", "c"];
for (const index in arr) {
console.log(index); // 輸出:0, 1, 2(索引,不是值)
}
// 比較:for...of vs for...in
const numbers = [10, 20, 30];
for (const num of numbers) {
console.log(num); // 10, 20, 30(值)
}
for (const index in numbers) {
console.log(index); // 0, 1, 2(索引)
}
// 建議:
// - 物件用 for...in
// - 陣列用 for...of 或陣列方法
break 和 continue
控制迴圈執行流程。
break:立即跳出迴圈
// 找到第一個偶數就停止
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) {
console.log(`找到第一個偶數:${i}`);
break; // 跳出迴圈
}
}
// 輸出:找到第一個偶數:2
// 巢狀迴圈中的 break
for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
if (j === 2) {
break; // 只跳出內層迴圈
}
console.log(`i=${i}, j=${j}`);
}
}
// 搜尋範例
const users = ["Alice", "Bob", "Charlie", "David"];
const target = "Charlie";
let found = false;
for (const user of users) {
if (user === target) {
console.log(`找到使用者:${user}`);
found = true;
break;
}
}
if (!found) {
console.log("找不到使用者");
}
continue:跳過本次迭代
// 只印出奇數
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) {
continue; // 跳過偶數
}
console.log(i);
}
// 輸出:1, 3, 5, 7, 9
// 過濾無效資料
const scores = [85, null, 92, undefined, 88, NaN, 76];
for (const score of scores) {
if (score === null || score === undefined || isNaN(score)) {
continue; // 跳過無效資料
}
console.log(score);
}
// 輸出:85, 92, 88, 76
// 跳過特定條件
const words = ["apple", "banana", "apricot", "cherry"];
for (const word of words) {
if (!word.startsWith("a")) {
continue; // 跳過不是 a 開頭的字
}
console.log(word);
}
// 輸出:apple, apricot
陣列迭代方法(複習與比較)
雖然不是傳統迴圈,但經常用來取代迴圈。
const numbers = [1, 2, 3, 4, 5];
// forEach:遍歷
numbers.forEach(num => {
console.log(num);
});
// map:轉換
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter:過濾
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4]
// reduce:歸納
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
// 何時使用傳統迴圈 vs 陣列方法?
// 傳統迴圈:需要提早跳出(break)、複雜的流程控制
// 陣列方法:簡單的資料處理、函數式程式設計風格
實戰練習
練習 1:找出質數
function isPrime(n) {
if (n <= 1) return false;
if (n === 2) return true;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (n % i === 0) {
return false;
}
}
return true;
}
// 找出 1 到 50 的所有質數
const primes = [];
for (let i = 1; i <= 50; i++) {
if (isPrime(i)) {
primes.push(i);
}
}
console.log(primes);
// [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
練習 2:反轉字串
function reverseString(str) {
let reversed = "";
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i];
}
return reversed;
}
console.log(reverseString("Hello")); // "olleH"
// 或使用 for...of
function reverseString2(str) {
let reversed = "";
for (const char of str) {
reversed = char + reversed;
}
return reversed;
}
練習 3:階乘計算
function factorial(n) {
let result = 1;
for (let i = 1; i <= n; i++) {
result *= i;
}
return result;
}
console.log(factorial(5)); // 120 (5 * 4 * 3 * 2 * 1)
console.log(factorial(10)); // 3628800
練習 4:費氏數列
function fibonacci(n) {
const sequence = [0, 1];
for (let i = 2; i < n; i++) {
sequence[i] = sequence[i - 1] + sequence[i - 2];
}
return sequence;
}
console.log(fibonacci(10));
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
練習 5:星號圖案
// 直角三角形
function printTriangle(height) {
for (let i = 1; i <= height; i++) {
let line = "";
for (let j = 1; j <= i; j++) {
line += "*";
}
console.log(line);
}
}
printTriangle(5);
// *
// **
// ***
// ****
// *****
// 金字塔
function printPyramid(height) {
for (let i = 1; i <= height; i++) {
let spaces = " ".repeat(height - i);
let stars = "*".repeat(2 * i - 1);
console.log(spaces + stars);
}
}
printPyramid(5);
// *
// ***
// *****
// *******
// *********
練習 6:兩數之和
// 找出陣列中兩個數字相加等於目標值的組合
function twoSum(nums, target) {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
return null;
}
const numbers = [2, 7, 11, 15];
console.log(twoSum(numbers, 9)); // [0, 1](2 + 7 = 9)
console.log(twoSum(numbers, 26)); // [2, 3](11 + 15 = 26)
練習 7:資料處理
const students = [
{ name: "Alice", score: 85 },
{ name: "Bob", score: 92 },
{ name: "Charlie", score: 78 },
{ name: "David", score: 95 },
{ name: "Eve", score: 88 }
];
// 計算平均分數
let totalScore = 0;
for (const student of students) {
totalScore += student.score;
}
const average = totalScore / students.length;
console.log(`平均分數:${average}`); // 87.6
// 找出最高分的學生
let topStudent = students[0];
for (const student of students) {
if (student.score > topStudent.score) {
topStudent = student;
}
}
console.log(`最高分:${topStudent.name} - ${topStudent.score}`);
// 最高分:David - 95
// 分級統計
const grades = { A: 0, B: 0, C: 0, D: 0, F: 0 };
for (const student of students) {
if (student.score >= 90) grades.A++;
else if (student.score >= 80) grades.B++;
else if (student.score >= 70) grades.C++;
else if (student.score >= 60) grades.D++;
else grades.F++;
}
console.log(grades); // { A: 2, B: 2, C: 1, D: 0, F: 0 }
效能考量
const arr = new Array(1000000).fill(0).map((_, i) => i);
// 不好:每次都計算 length
console.time("bad");
for (let i = 0; i < arr.length; i++) {
// 處理資料
}
console.timeEnd("bad");
// 好:快取 length
console.time("good");
const len = arr.length;
for (let i = 0; i < len; i++) {
// 處理資料
}
console.timeEnd("good");
// 更好:for...of(語法簡潔且效能好)
console.time("better");
for (const item of arr) {
// 處理資料
}
console.timeEnd("better");
// 注意:現代 JavaScript 引擎已經很聰明
// 在大多數情況下,可讀性比微小的效能差異更重要
迴圈選擇指南
// 已知重複次數 → for
for (let i = 0; i < 10; i++) {
// 執行 10 次
}
// 不確定次數,只知道終止條件 → while
while (condition) {
// 直到條件為 false
}
// 至少執行一次 → do...while
do {
// 至少執行一次
} while (condition);
// 遍歷陣列的值 → for...of
for (const item of array) {
// 處理每個元素
}
// 遍歷物件的屬性 → for...in
for (const key in object) {
// 處理每個屬性
}
// 簡單的陣列處理 → 陣列方法
array.forEach(item => {
// 處理每個元素
});
常見錯誤
1. 無窮迴圈
// 錯誤:忘記遞增
let i = 0;
while (i < 5) {
console.log(i);
// 忘記 i++,會無限執行
}
// 正確
let i = 0;
while (i < 5) {
console.log(i);
i++;
}
2. 陣列越界
const arr = [1, 2, 3];
// 錯誤:使用 <= 會超出範圍
for (let i = 0; i <= arr.length; i++) {
console.log(arr[i]); // 最後一次會是 undefined
}
// 正確:使用 <
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
3. 迴圈中修改陣列
const numbers = [1, 2, 3, 4, 5];
// 危險:迴圈中刪除元素
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
numbers.splice(i, 1); // 會跳過元素
}
}
// 正確:倒著遍歷
for (let i = numbers.length - 1; i >= 0; i--) {
if (numbers[i] % 2 === 0) {
numbers.splice(i, 1);
}
}
// 更好:使用 filter
const odds = numbers.filter(n => n % 2 !== 0);
小結
這篇我們學習了:
- for、while、do...while 迴圈
- for...of 和 for...in 的差異
- break 和 continue 的使用
- 迴圈的實戰應用
- 效能考量與最佳實踐
下一步:
完成這篇後,你已經能夠: - 選擇適合的迴圈類型 - 處理重複性任務 - 避免常見的迴圈錯誤
前往下一篇:單元八:作用域與閉包
在那裡,我們會學習變數的可見範圍與進階概念。
0 留言
發表留言