← JavaScript Learning Path

JAVASCRIPT V2 · FOUNDATIONS · 38

Loops:重複不是重點,State Transition 才是重點

Loop 的真正模型是:初始化 state → 檢查 condition → 執行 body → 更新 state → 再檢查。只背 for (...) {} 很容易寫出 off-by-one、漏資料或 infinite loop。這一課要求你能逐輪 trace。

Learning outcomes

1. Why now:同一條規則不可能手動複製 100 次

const scores = [90, 55, 72, 88];

上一課會判斷一個 score。真實程式會遇到一批 records。Loop 讓同一段邏輯依序處理資料,而不是複製四份、四百份程式碼。

current state→condition→body→update→next state

2. for 的四個時間點

for (let i = 0; i < 3; i++) {
  console.log(i);
}
  1. let i = 0:只執行一次。
  2. i < 3:每輪開始前檢查。
  3. body:condition 為 true 才執行。
  4. i++:每輪 body 後更新,再回到 condition。

3. Worked Example A:完整 Trace,而不是只背輸出

檢查次數ii < 3body outputupdate
10true0i → 1
21true1i → 2
32true2i → 3
43false停止—

注意最後一次 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;
}
輪次scoretotal beforetotal after
190090
25590145
372145217

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 時固定問:

  1. 初始 state?
  2. 每輪怎麼更新?
  3. 哪個 state 才會讓 condition false?
  4. 目前 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) // 0

16. 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

18. Further reading

Knowledge check

  1. 描述 for 四個時間點。
  2. 解釋為什麼 i<3 和 i<=3 差一輪。
  3. 說出 accumulator 在 sum/count 中扮演什麼角色。
  4. 看到 infinite loop 時列出四個 debugging 問題。
  5. 完成 mastery exercise 並讓 inclusive range tests 全 PASS。