iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
JavaScript

JS 核心重構:勇者轉職傳說系列 第 27

Day 26:【效能神兵】防抖 (Debounce) 與節流 (Throttle) 的冷卻機制

  • 分享至 

  • xImage
  •  

本文同步分享於個人部落格:Liwen Chiou | Digital Architect & Full-Stack Engineer


📜 這裡的修煉規則:

孩子,在踏上今日的領地前,先坐下來喝杯酒吧。
修煉的路很長,別急著奔跑,先聽聽這份卷軸裡的古老叮嚀。

我在每一站的盡頭都為你鋪設了 實戰演武場 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) 事件、射擊遊戲、搶票按鈕。
  • 核心邏輯:在計時期間內,直接無視所有的觸發請求。

🏹 導師的辨析圖:防抖 vs 節流

特性 防抖 (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 是如何在閉包中被 clearset 的,你才算真正掌握了 JS 的時序控制。


🛖 【營火叮嚀:導師的經驗談】

記得我第一次接捲軸事件 (Scroll) 時,為了做一個導覽列變色的效果,我直接在 scroll 事件裡跑了一個沉重的計算。結果網頁滑起來就像卡在泥淖裡的牛車。

後來前輩告訴我:「導師啊,瀏覽器一秒鐘可以偵測到幾百次捲軸移動,但你的螢幕更新率才 60Hz,你運算那麼多次給誰看呢?」

那天我學會了 Throttle。從此,我的代碼滑順得像抹了凡士林。


天色微亮,營火雖已燃盡,但你眼中卻閃爍著領悟的光芒。在前往演武場挑戰關卡前,我已經幫你把靈魂碎片精煉成了這份「戰術錦囊」。拿好它,今日的任務不再是負擔,而是你證明自我的舞台。去吧,公會的英雄榜在等著你的戰報。

📝 【夥伴筆記:今日修煉精華】

  • 防抖 (Debounce):重新計時。適用於打字、調整視窗。只拿最後一次。
  • 節流 (Throttle):固定頻率。適用於捲軸、按鈕連點。
  • 底層核心:利用 閉包 保存 timer 狀態,利用 setTimeout 控制執行時機。

🎯 【實戰演武場】

⚔️ 任務鑑定條件:

  1. 完成 📜 本日實戰任務 (CodePen)
  2. 將 CodePen 網址貼至 QuestBoard,並回填鑑定報告:
    • 初心者:冷卻大師 (成就感發掘)
        1. 能成功防止按鈕連點導致的重複 Log 嗎?
    • 冒險者:時序操控者 (理性挑戰)[⚡ 觀念辨析]
        1. 如果設定 delay 為 1000ms,快速點擊按鈕 5 次(每次間隔 200ms),Debounce 與 Throttle 分別會執行幾次?

📚 【圖書館卷軸:延伸學習】


上一篇
Day 25:結業實戰:打造你的異世界氣象觀測站
系列文
JS 核心重構:勇者轉職傳說27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言