JAVASCRIPT V2 · FOUNDATIONS · 38
Loops:重複不是重點,State Transition 才是重點
Loop 的真正模型是:初始化 state → 檢查 condition → 執行 body → 更新 state → 再檢查。只背 for (...) {} 很容易寫出 off-by-one、漏資料或 infinite loop。這一課要求你能逐輪 trace。
Learning outcomes
- 拆解
for的 initializer、condition、update。 - 逐輪追蹤 loop state 與 accumulator。
- 區分 index-based iteration 與 value-based iteration。
- 理解
break、continue與 termination。 - 能用 trace table 找 off-by-one 與 infinite loop。
1. Why now:同一條規則不可能手動複製 100 次
const scores = [90, 55, 72, 88];
上一課會判斷一個 score。真實程式會遇到一批 records。Loop 讓同一段邏輯依序處理資料,而不是複製四份、四百份程式碼。
2. for 的四個時間點
for (let i = 0; i < 3; i++) {
console.log(i);
}
let i = 0:只執行一次。i < 3:每輪開始前檢查。- body:condition 為 true 才執行。
i++:每輪 body 後更新,再回到 condition。
3. Worked Example A:完整 Trace,而不是只背輸出
| 檢查次數 | i | i < 3 | body output | update |
|---|---|---|---|---|
| 1 | 0 | true | 0 | i → 1 |
| 2 | 1 | true | 1 | i → 2 |
| 3 | 2 | true | 2 | i → 3 |
| 4 | 3 | false | 停止 | — |
注意最後一次 condition check 仍然會發生,只是 body 不再執行。
4. Off-by-one:< 和 <= 差一個 iteration
for (let i = 1; i <= 3; i++) {
console.log(i);
}
// 1, 2, 3
for (let i = 1; i < 3; i++) {
console.log(i);
}
// 1, 2
看到「少一筆」或「多跑一筆」,優先檢查 initialization 與 boundary operator。
5. for...of:當你要的是 value,不是 index
const scores = [90, 55, 72];
for (const score of scores) {
console.log(score);
}
每輪 score 依序取得 90、55、72。處理 records 時,這通常比自己管理 index 更直接。
如果你真的需要 index,例如顯示第幾筆,才使用 index-based loop 或其他適合的 API。
6. Accumulator:Loop 最常見的 State Pattern
const scores = [90, 55, 72];
let total = 0;
for (const score of scores) {
total = total + score;
}
| 輪次 | score | total before | total after |
|---|---|---|---|
| 1 | 90 | 0 | 90 |
| 2 | 55 | 90 | 145 |
| 3 | 72 | 145 | 217 |
sum、count、平均、grouping 都是在不同形式下累積 state。
7. Worked Example B:同一輪可以更新多個 accumulator
let total = 0;
let passedCount = 0;
for (const score of scores) {
total += score;
if (score >= 60) {
passedCount++;
}
}
每輪先處理一筆資料,再把「總和」與「及格數」兩種 state 更新。Loop body 應該能被你用自然語言描述。
8. while:當 iteration 次數不是一開始就知道
let attempts = 0;
while (attempts < 3) {
attempts++;
}
while 的 mental model仍然一樣:state、condition、body、update。只是不把三部分集中寫在 header。
9. Infinite loop:Termination condition 永遠到不了
let x = 1;
while (x > 0) {
x++;
}
x 只會越來越大,因此 x > 0 永遠 true。Debug 時固定問:
- 初始 state?
- 每輪怎麼更新?
- 哪個 state 才會讓 condition false?
- 目前 update 有可能到那個 state 嗎?
10. break 與 continue:改變預設控制流程
for (const score of scores) {
if (score < 0) break;
if (score < 60) continue;
console.log("passed:", score);
}
continue 跳到下一輪;break 結束整個 loop。兩者都會讓 trace 變得更重要。
11. Worked Example C:Inclusive Range
function sumRange(start, end) {
let total = 0;
for (let n = start; n <= end; n++) {
total += n;
}
return total;
}
sumRange(3, 5) 的 state:
0 → 3 → 7 → 12
這個例子故意驗證 <=;如果寫成 <,最後的 5 會被漏掉。
12. Common mistakes
A. 忘記 update
condition 永遠不改,造成 infinite loop。
B. 邊界少一筆
< / <= 與 start value 沒對齊。
C. 把 index 當成 value
for (let i = 0; i < scores.length; i++) {
console.log(i); // index
console.log(scores[i]); // value
}
D. 在 loop 裡意外重設 accumulator
for (const score of scores) {
let total = 0; // 每輪都歸零
total += score;
}13. Debug evidence:先印 state,不要重寫整段
for (let n = start; n <= end; n++) {
console.log({ n, totalBefore: total });
total += n;
console.log({ totalAfter: total });
}
Log 的目的不是永久留在產品,而是快速回答「哪一輪開始偏離 expected state」。
14. Guided exercise:Trace before run
let total = 1;
for (let i = 1; i <= 3; i++) {
total = total * 2;
}
先畫四欄:i、condition、total before、total after。最後才執行確認。
15. Independent exercise:實作 sumRange(start,end)
需求:包含 start 與 end。至少測:
sumRange(1, 1) // 1
sumRange(1, 5) // 15
sumRange(3, 5) // 12
sumRange(-2, 2) // 016. Challenge:什麼時候不要用 nested loop?
for (const a of listA) {
for (const b of listB) {
// compare
}
}
這裡可能做 listA.length × listB.length 次工作。現在先建立敏感度:nested loop 不是「一定錯」,但資料變大時應問工作量怎麼成長。75 Big-O 會正式處理。
Project checkpoint:班級統計
const scores = [90, 55, 72, 88];
let total = 0;
let passedCount = 0;
for (const score of scores) {
total += score;
if (score >= 60) passedCount++;
}
const average =
scores.length ? total / scores.length : 0;
你現在能把上一課的一筆規則套到多筆資料。下一課把這些行為封裝成 function。
17. Vocabulary / Summary
- iteration:loop 的一次執行輪次。
- accumulator:跨 iteration 保留並更新的 state。
- termination:condition 最終變成 false 而停止。
- off-by-one:邊界差一筆的錯誤。
- index:位置,不等於資料 identity。
18. Further reading
Knowledge check
- 描述
for四個時間點。 - 解釋為什麼
i<3和i<=3差一輪。 - 說出 accumulator 在 sum/count 中扮演什麼角色。
- 看到 infinite loop 時列出四個 debugging 問題。
- 完成 mastery exercise 並讓 inclusive range tests 全 PASS。