--- title: 'Debouncing and Throttling' disqus: hackmd --- Debouncing and Throttling === ![downloads](https://img.shields.io/github/downloads/atom/atom/total.svg) ![build](https://img.shields.io/appveyor/ci/:user/:repo.svg) ![chat](https://img.shields.io/discord/:serverId.svg) ## Table of Contents [TOC] 前言 --- 在網頁移動滑鼠、滾動、更改視窗大小、鍵盤事件(mousemove、scroll、resize、keydown/keyup/keypress)等事件通常都可以及時回應使用者,觸發頻率比較高,可是有時候我們並不需要一直計算,可能會造成頁面緩慢,或是瀏覽器崩潰。 就好比移動滑鼠來說,所需要的回應可能是停留某些地方,但我們在移動滑鼠的時候,觸發頻率很高,在不斷監聽之下很容易浪費很多資源。 滑鼠移動網址測試(mousemove) https://codepen.io/llh911001/pen/XmGMxg?editors=101 比較有名的例子:2011 年時,Twitter 頁面 scroll 時會變得緩慢: John Resig - Learning from Twitter https://johnresig.com/blog/learning-from-twitter/ John建議解决方式是,在 onScroll 事件外部,每 250ms 執行一次。解法簡單,給用戶比較良好體驗。 目前比較常用的解法有throttling 和 debouncing 等等。 視覺化模擬 --- 根據網頁可以知道這兩種的差異。debounce 強制函数在某段時間只執行一次,throttle 強制函数以固定的速率執行。在處理一些高頻率觸發的 DOM 事件的时候,它們都能極大提高用户體驗。 debounce and throttle http://demo.nimius.net/debounce_throttle/ Debouncing(防抖動) --- 模仿電器開關處理的方法,把多個訊號合併成一個訊號。讓一個函式在連續觸發時只執行一次。一個常見的用法是使用者連續輸入基本資訊後,才觸發事件處理器進行格式確認。 ```gherkin= ** * * @param fn {Function} 實際要執行的函數 * @param delay {Number} 延遲時間,單位是毫秒(ms) * * @return {Function} 返回一個“去彈跳”了的函数 */ function debounce(fn, delay) { // 定時器,用来 setTimeout var timer // 返回一個函数,這個函数會在一個時間區間结束後的 delay 毫秒時執行 fn 函数 return function () { // 保存函数調用時的上下文和參數,傳遞给 fn var context = this var args = arguments // 每次這個返回的函数被調用,就清除定時器,以保證不執行 fn clearTimeout(timer) // 當返回的函数被最後一次調用後(也就是用户停止了某个連續的操作), // 再過 delay 毫秒就執行 fn timer = setTimeout(function () { fn.apply(context, args) }, delay) } } function debounce(func, delay) { var timer = null; return function () { var context = this; var args = arguments; clearTimeout(timer); timer = setTimeout(function () { func.apply(context, args) }, delay); } } ``` >程式碼很直觀,先設一個計時器 (timer),保存當下脈絡後 (context, args),只要太早進來 (小於 delay) 就會重置計時器,直到成功執行 setTimeout 內的函式後結束。 >注意這裡 debounce 回傳的是一個閉包 (closure),是 js 的一個重要特性,不這樣寫的話 timer 就必須是全域變數,以防止每次呼叫 timer 都被重置產生錯誤。 感受一下 https://codepen.io/llh911001/pen/EVMLJw?editors=101 Throttling(函數節流) --- 函數節流讓一個函數不要執行得太頻繁,也就是控制函數最高呼叫頻率,減少一些過快的呼叫來節流。一個常見的用法是減少 scroll 的觸發頻率,因為 scroll 常常綁定一些消耗資源的 render 的事件。 ```gherkin= /** * * @param fn {Function} 實際要執行的函数 * @param delay {Number} 執行間隔,單位是毫秒(ms) * * @return {Function} 返回一个“節流”函数 */ function throttle(fn, threshold) { // 紀錄上次執行的时间 var last // 定时器 var timer // 默認間隔為 250ms threshold || (threshold = 250) // 返回的函数,每過 threshold 毫秒就執行一次 fn 函数 return function () { // 保存函数調用時的上下文和參數,傳遞给 fn var context = this var args = arguments var now = +new Date() // 如果距離上次執行 fn 函数的時間小於 threshold,那麼就放棄 // 執行 fn,並重新計時 if (last && now < last + threshold) { clearTimeout(timer) // 保證在當前時間區間结束後,再執行一次 fn timer = setTimeout(function () { last = now fn.apply(context, args) }, threshold) // 在時間區間的最開始和到達指定間隔的时候執行一次 fn } else { last = now fn.apply(context, args) } } } function throttle(func, threshhold) { var last, timer; if (threshhold) threshhold = 250; return function () { var context = this var args = arguments var now = +new Date() if (last && now < last + threshhold) { clearTimeout(timer) timer = setTimeout(function () { last = now func.apply(context, args) }, threshhold) } else { last = now fn.apply(context, args) } } } ``` >與 debouncing 的程式邏輯相似,只多了一個時間間隔的判斷。 感受一下 https://codepen.io/llh911001/pen/MaxXPV?editors=101 Debounce 和 Throttle 的 library --- >Lodash https://lodash.com/ >Underscore http://underscorejs.org/ 參考資料 --- 網頁 DOM 事件的效能優化:Debounce 和 Throttle https://mropengate.blogspot.com/2017/12/dom-debounce-throttle.html 實例解析防抖動(Debouncing)和節流閥(Throttling) https://jinlong.github.io/2016/04/24/Debouncing-and-Throttling-Explained-Through-Examples/ Debounce 和 Throttle 的原理及實現 http://hackll.com/2015/11/19/debounce-and-throttle/ ## Appendix and FAQ :::info **Find this document incomplete?** Leave a comment! ::: ###### tags: `Debouncing` `Throttling`