---
title: 'Debouncing and Throttling'
disqus: hackmd
---
Debouncing and Throttling
===



## 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`