用生活中的例子理解防抖與節流 Debounce & Throttle in daily life

用生活中的例子理解防抖與節流 Debounce & Throttle in daily life

關於 Debounce & Throttle 正經詳細的教學已經不少
如果還是無法完全體會,那麼不妨換一個角度來理解看看
這篇文章會用搭公車與接電話來比喻兩者的運作邏輯
也許能加深印象!

Debounce & Throttle 是什麼功能

網頁中常常會有 監聽A事件 => 產生B動作 的情境

  • 監聽瀏覽器捲軸的滾動事件,並顯示動畫
  • 監聽input內容,並產生自動補全

如果沒有做 Debounce 或 Throttle
就會因為監聽到時時刻刻的變化,並產生動作而消耗大量效能
例如: 只是滾動捲軸就可以產生數十到數百個事件

《Debouncing and Throttling Explained Through Examples》by David Corbacho
https://css-tricks.com/debouncing-throttling-explained-examples/

而實際應用中,我們不需要緊迫盯人,時時刻刻都在監看變化
不但浪費效能,甚至會給使用者”喂! 我還沒完成 幹嘛亂動”的感覺
例如:

  • 輸入框的自動自動補全,可以等到使用者打字停止後200毫秒後再顯示
  • 每過200毫秒,再檢查一次捲軸滾動的位置是否要顯示動畫了

所以 Debounce 與 Throttle 都是節省效能的作法

Debounce 防抖實作與生活舉例

Debounce 的實作,在生活中就好像在搭公車一樣
公車停在站牌後,會等大家排隊上車
等到大家上車後,才一次出發

  • 排隊上車的人 => 監聽的事件,如: 捲軸滾動、輸入框打字
  • 公車出發 => 對應的事件,如: 顯示動畫、顯示自動補全

而程式碼的邏輯則是
接收到執行的指令時,會延遲一段時間才執行
如果這段時間內又被觸發,就重新倒數

// debounce 實作
const debounce = (fn, delay) => {
  let timeoutID = null;
  return (...args) => {
    clearTimeout(timeoutID);
    timeoutID = setTimeout(() => {
      fn(...args);
    }, delay);
  };
};

完整範例程式碼,可以動手玩玩:

Throttle 節流實作與生活舉例

Debounce 的實作,在生活中就好像客服在接電話一樣
有人打電話進來後,就會進入忙線狀態(假設每通電話時間固定)
忙線時,再有其他人打進來也不會理會

直到時間結束才會接下一通處理

  • 打進來的人 => 監聽的事件,如: 捲軸滾動、輸入框打字
  • 接通處理 => 對應的事件,如: 顯示動畫、顯示自動補全

而程式碼的邏輯
除了接電話,撰文時也覺得其實很像遊戲放技能
接收到執行的指令時,會檢查是不是在忙線中、CD是不是還在冷卻
如果這段時間內又監聽到事件,就不會觸發後續動作,節省效能
直到冷卻完成才能再接下一通、才能再放技能

// throttle 實作
const throttle = (fn, delay) => {
  let timeoutID = null;
  return (...args) => {
    if (timeoutID) return;
    timeoutID = setTimeout(() => {
      fn(...args);
      timeoutID = null;
    }, delay);
  };
};

完整範例程式碼,可以動手玩玩:

註記與結語

本篇的範例是比較單純的 Debounce & Throttle
也是為了方便初學的人學習
在 loadash, underscore 等函式庫中
提供了其他參數,可以應用更廣泛的情境
例如: leading 參數可以讓 Debounce 變成先執行動作後,才進入延遲CD
所以延遲與執行的先後,不能當作區別兩者的方式
兩者最大的區別

  • Debounce 是把一連串的動作,變成只執行一次
  • Throttle 即使一連串動作超長,每隔一段緩衝時間,還是會執行一次

了解了這兩種方式後,除了輸入框與捲動軸以外
例如:拖曳物件、需要消耗大量效能的畫面渲染(圖表、地圖的互動)、避免重複發送大量 API request
都可以考慮加入 Debounce & Throttle 節省效能的設計
幫助你的網頁體驗與精緻度大幅提升!

參考資料