在前端開發中,我們經常會遇到需要監聽使用者頻繁操作的場景,例如在搜尋框連續輸入文字,或是快速滾動網頁捲軸。如果每次操作都直接觸發繁重的運算或 API 請求,將會對伺服器與瀏覽器效能造成巨大壓力。本章節我們將探討如何運用「防抖 (Debounce)」與「節流 (Throttle)」這兩項冷卻機制,結合閉包與計時器的概念,來精準控制函式的執行頻率,確保應用程式保持流暢與穩定。
Day 26: 【效能神兵】防抖 (Debounce) 與節流 (Throttle) —— 的冷卻機制
本文同步分享於2026鐵人賽:JS 核心重構:勇者轉職傳說
孩子,在踏上今日的領地前,先坐下來喝杯酒吧。 修煉的路很長,別急著奔跑,先聽聽這份卷軸裡的古老叮嚀。
我在每一站的盡頭都為你鋪設了 「實戰演武場 CodePen」。那不只是程式碼的拼湊,而是你在這場暴風雨中,試圖用邏輯點燃的一顆星火。
成功突圍後,別忘了帶著你的戰利品回到 「QuestBoard 佈告欄」。在那裡,你會發現自己從不孤單,公會的夥伴們都正舉杯等待你的歸來。
記住...
「 導師看重的從來不是你的劍法有多華麗,而是當你被邏輯擊潰、滿身泥濘後,依然選擇握緊理性的劍柄,再次向真相發起衝鋒的勇氣。 」
🛡️ 【公會大廳:導師的修煉導讀】
「導師,我實作的搜尋框出事了!」
一名年輕的冒險者氣喘吁吁地跑進大廳。「我串接了昨天學的氣象站 API,但我發現使用者每打一個字,信鴿(Fetch)就飛出去一次。如果使用者打字很快,一秒鐘就會飛出十幾隻信鴿... 氣象伺服器的守衛已經快把我拉黑了!」
我放下手中的鵝毛筆,示意他冷靜。
「孩子,這就是為什麼你需要 『技能冷卻 (Cooldown)』。在真實世界中,如果你對著怪物不停地按普通攻擊,你的身體也不可能一秒鐘揮劍一千次,對吧?我們需要一種機制,來過濾那些『無意義的頻繁觸發』。」
今天,我要教你兩大神兵利器:防抖 (Debounce) 與 節流 (Throttle)。
💡 【導師講義:底層真相探究】
這兩個觀念雖然聽起來很專業,但其實就是我們在 Day 07 學過的「閉包 (Closure)」 與 Day 17 學過的「計時器 (setTimeout)」 的完美結合。
1. 防抖 (Debounce):回城讀條
想像你在施放一個「回城捲軸」,需要 3 秒鐘的讀條時間。 如果你在讀條到第 2 秒時,不小心被怪物碰了一下(或者你又按了一次按鍵),讀條就會重頭開始。只有當你「完全安靜下來」並維持 3 秒後,傳送才會成功。
- 適用場景:搜尋框輸入、視窗大小縮放 (Resize)。
- 核心邏輯:只要有新觸發,就清掉舊的計時器,重新計時。
2. 節流 (Throttle):攻擊 CD
想像你在揮舞一把重劍,揮一下需要 1 秒鐘的冷卻時間。 在這 1 秒內,不論你瘋狂點擊多少次滑鼠,劍都只會在那 1 秒結束後才揮出下一發。
- 適用場景:捲軸滾動 (Scroll) 事件、射擊遊戲、搶票按鈕。
- 核心邏輯:在計時期間內,直接無視所有的觸發請求。
| 特性 | 防抖 (Debounce) | 節流 (Throttle) |
|---|---|---|
| 目標焦點 | 等待最後一次 | 維持穩定頻率 |
| 觸發時機 | 停止動作後才執行 | 定期執行 |
| 比喻 | 影印回城讀條 | 技能冷卻 CD |
3. 圖解閉包守衛
為什麼這需要閉包?因為我們需要一個「記憶空間」來儲存計時器的狀態 (timer),而這個狀態不能被外圍干擾。

⚔️ 【戰術對抗:學長與學弟的代碼對決】
招式示範:搜尋框監聽
❌ 冒險學弟:直觀但耗能的做法
// 每按一個鍵就發送一次請求
input.addEventListener("input", (e) => {
console.log("📡 發送信鴿獲獲取資料...", e.target.value);
});
- 負能量評級:🔴 伺服器壓力爆表 / 🔴 浪費頻寬
✅ 勇者學長:優雅的防抖控制
// 實作一個簡易防抖大師
function debounce(fn, delay) {
let timer = null; // 這是閉包住的秘密記憶
return function (...args) {
clearTimeout(timer); // 只要被戳,就重來
timer = setTimeout(() => {
fn.apply(this, args); // 💡 確保傳回原本的主人與參數 (Day 10 複習)
}, delay);
};
}
const smartSearch = debounce((val) => {
console.log("✨ [精準發射] 獲取資料:", val);
}, 500);
input.addEventListener("input", (e) => smartSearch(e.target.value));
- 利潤評級:🟢 邏輯極簡 / 🟢 充滿維護彈性
注意看 let timer = null 的位置。它被鎖在 debounce 函數中,卻能被回傳的那個匿名函數持續存取。這就是我們在 Day 07 埋下的伏筆 —— 閉包的真正威力。
🏰 【勇者精英課:邁向職人的進階架構】
在真實專案中,我們很少自己從頭寫防抖與節流。職人們通常會使用像是 Lodash 這樣的工具庫。
// 職人的作法
import { debounce } from "lodash";
const search = debounce(fetchData, 300);
但在轉職前,你必須親手寫過一次。因為當你理解了 timer 是如何在閉包中被 clear 與 set 的,你才算真正掌握了 JS 的時序控制。
記得我第一次接捲軸事件 (Scroll) 時,為了做一個導覽列變色的效果,我直接在 scroll 事件裡跑了一個沉重的計算。結果網頁滑起來就像卡在泥淖裡的牛車。
後來前輩告訴我:「導師啊,瀏覽器一秒鐘可以偵測到幾百次捲軸移動,但你的螢幕更新率才 60Hz,你運算那麼多次給誰看呢?」
那天我學會了 Throttle。從此,我的代碼滑順得像抹了凡士林。
天色微亮,營火雖已燃盡,但你眼中卻閃爍著領悟的光芒。在前往演武場挑戰關卡前,我已經幫你把靈魂碎片精煉成了這份「戰術錦囊」。拿好它,今日的任務不再是負擔,而是你證明自我的舞台。去吧,公會的英雄榜在等著你的戰報。
📝 【夥伴筆記:今日修煉精華】
這份筆記是你的隨身護身符,卡關時看一眼,真相就在裡面。
- 防抖 (Debounce):重新計時。適用於打字、調整視窗。只拿最後一次。
- 節流 (Throttle):固定頻率。適用於捲軸、按鈕連點。
- 底層核心:利用 閉包 保存
timer狀態,利用setTimeout控制執行時機。
🎯 【實戰演武場】
⚔️ 任務鑑定條件:
- 完成 📜 本日實戰任務 (CodePen)。
- 將 CodePen 網址貼至 QuestBoard,並回填鑑定報告:
- 初心者:冷卻大師 (成就感發掘):
-
- 能成功防止按鈕連點導致的重複 Log 嗎?
-
- 冒險者:時序操控者 (理性挑戰):[⚡ 觀念辨析]
-
- 如果設定
delay為 1000ms,快速點擊按鈕 5 次(每次間隔 200ms),Debounce 與 Throttle 分別會執行幾次?
- 如果設定
-
- 初心者:冷卻大師 (成就感發掘):
📚 【圖書館卷軸與前後站連結】
- 前導冒險: Day 00: 轉職冒險序章
- 上一站冒險: Day 25: 模組化開發
- 下一站冒險: Day 27: 資料驅動
- MDN 官方參考手冊: setTimeout - MDN