← JavaScript Learning Path

JAVASCRIPT V2 · ADVANCED · 125

Higher-Order Functions / Callbacks:Function 也能成為資料與策略

你早就用過 event listener、array methods、timer 與 Promise。這些 API 的共同點是:它們接收 function,之後在特定條件下呼叫它。這一課要把「callback」從一個名詞變成可追蹤的 execution contract。

Learning outcomes

1. Why now:你已經用了很多 callback,只是還沒統一理解

button.addEventListener(
  "click",
  handleClick
);

scores.map(
  score => score * 2
);

setTimeout(
  () => console.log("later"),
  1000
);

三段程式看起來屬於不同領域,但都在做同一件事:把「一段之後要執行的行為」以 function value 的形式交給另一個 function。

2. Mental model:Function value 與 Function call 是兩回事

function greet(name) {
  return "Hello " + name;
}

const a = greet;
const b = greet("Amy");
binding保存什麼
afunction 本身
b呼叫 greet 後得到的 string

看到括號時要立刻問:「這裡是在傳 function,還是在執行 function?」這是 callback bug 最常見的第一分界。

3. Worked Example A:自己寫 forEachLike

function forEachLike(
  items,
  action
) {
  for (const item of items) {
    action(item);
  }
}

forEachLike(
  ["A", "B", "C"],
  value => console.log(value)
);

forEachLike 管理 iteration;caller 注入每一輪要做的 action。這就是 higher-order function:接收或回傳 function。

4. Callback contract:不是所有 callback 都回同一種東西

students.filter(
  student => student.score >= 60
);

students.map(
  student => student.name
);

students.sort(
  (a, b) => b.score - a.score
);
APIcallback inputcallback output
filteritemBoolean-like keep/drop decision
mapitemtransformed value
sorta, bnegative / zero / positive ordering signal

5. Worked Example B:Higher-Order Function 回傳 Function

function makeMinimumScore(
  minimum
) {
  return function passes(
    student
  ) {
    return student.score
      >= minimum;
  };
}

const passes80 =
  makeMinimumScore(80);

students.filter(passes80);

Higher-order 不只代表「收 callback」;回傳 function 也算。這會直接銜接下一課 closure。

6. Execution trace:callback 到底由誰呼叫?

const doubled =
  [10, 20, 30].map(
    n => n * 2
  );
map owns iteration

round 1:
n = 10
callback returns 20

round 2:
n = 20
callback returns 40

round 3:
n = 30
callback returns 60

result:
[20, 40, 60]

關鍵不是「arrow function 很短」,而是 map 控制何時呼叫 callback、傳什麼參數、如何使用 callback return value。

7. Worked Example C:Event callback 沒有 return pipeline

button.addEventListener(
  "click",
  event => {
    console.log(event.type);
    return 123;
  }
);

這裡 return 123 並不會自動變成 click event 的「結果」。不同 higher-order APIs 對 callback return value 的 contract 不同,不能把 map 的直覺套到所有 callback。

8. Common mistakes

A. 把 callback 先呼叫掉

setTimeout(
  console.log("later"),
  1000
);

console.log 現在就執行,傳給 setTimeout 的是 undefined。

B. 以為 callback 永遠同步

map callback 通常同步逐項跑;timer/event/Promise callback 則由排程決定。

C. 忽略 callback contract

filter callback 忘記 return Boolean-like value,可能得到空結果或錯誤結果。

D. 為了「函數式」把所有邏輯塞進一行

Higher-order abstraction 的目標是責任清楚,不是 code golf。

9. Debug evidence:四個固定問題

Callback:
1. Who stores it?
2. Who calls it?
3. When is it called?
4. What input/output contract?

如果 callback「沒有跑」,先找註冊證據;如果「跑了但資料錯」,看實際 input;如果「結果沒被採用」,檢查 host API 是否真的會消費 return value。

10. Guided exercise:寫 filterLike

function filterLike(
  items,
  predicate
) {
  // TODO
}

const passed =
  filterLike(
    students,
    student =>
      student.score >= 60
  );

要求:不使用原生 filter。每輪呼叫 predicate;只有 predicate 回傳 truthy 時把 item 放進新 array;不得修改原 array。

11. Independent exercise:排序策略注入

建立 sortStudents(students, compare),讓 caller 可以傳入「分數高到低」「姓名 A→Z」兩種 comparator。函式必須保留原陣列。

const byScore =
  (a, b) => b.score - a.score;

const byName =
  (a, b) =>
    a.name.localeCompare(b.name);

12. Challenge:回傳 validator function

寫 between(min,max),回傳一個接受 number 的 validator。接著建立 isValidScore = between(0,100)。下一課要解釋為什麼回傳的 function 還能存取 min/max。

Project checkpoint:Grade Tracker 可插拔策略

const filters = {
  all: () => true,
  passed: s => s.score >= 60,
  excellent: s => s.score >= 90
};

function visibleStudents(
  students,
  filterName
) {
  const predicate =
    filters[filterName];

  return students.filter(
    predicate
  );
}

Project 從「硬編碼 if」進化成行為策略 table;UI 只選策略名稱,不必知道 predicate 細節。

13. Vocabulary / Summary

14. Further reading

Knowledge check · Mastery

  1. 解釋 handleClick 與 handleClick() 的差異。
  2. 從 map/filter/sort 各說明 callback contract。
  3. 完成 filterLike 與可插拔排序器。
  4. 在頁面下方 Executable JS practice 中先 prediction 再執行。