JS函式防抖和函式節流
問題引入
問題1: 如果實現了dom拖拽功能,但是在繫結拖拽事件的時候發現每當元素稍微移動一點便觸發了大量的回撥函式,導致瀏覽器直接卡死,這個時候怎麼辦?
**問題2:**如果給一個按鈕繫結了表單提交的post事件,但是使用者有些時候在網路情況極差的情況下多次點選按鈕造成表單重複提交,如何防止多次提交的發生?
為了應對如上場景,便出現了函式防抖和函式節流兩個概念,總的來說:
這兩個方法是在時間軸上控制函式的執行次數。
函式防抖(debounce)
概念: 在事件被觸發n秒後再執行回撥,如果在這n秒內又被觸發,則重新計時。
生活中的例項: 如果有人進電梯(觸發事件),那電梯將在10秒鐘後出發(執行事件監聽器),這時如果又有人進電梯了(在10秒內再次觸發該事件),我們又得等10秒再出發(重新計時)。
函式節流(throttle)
概念: 規定一個單位時間,在這個單位時間內,只能有一次觸發事件的回撥函式執行,如果在同一個單位時間內某事件被觸發多次,只有一次能生效。
生活中的例項: 我們知道目前的一種說法是當 1 秒內連續播放 24 張以上的圖片時,在人眼的視覺中就會形成一個連貫的動畫,所以在電影的播放(以前是,現在不知道)中基本是以每秒 24 張的速度播放的,為什麼不 100 張或更多是因為 24 張就可以滿足人類視覺需求的時候,100 張就會顯得很浪費資源。
分析圖
假設,我們觀察的總時間為10秒鐘,規定1秒作為一次事件的最小間隔時間。
如果觸發事件的頻率是 0.5s/次
,那麼
函式防抖如圖
因為始終沒法等一秒鐘就被再次觸發了,所以最終沒有一次事件是成功的。
函式節流如圖
因為控制了最多一秒一次,頻率為0.5s/次
,所以每一秒鐘就有一次事件作廢。最終控制成1s/次
如果觸發事件的頻率是 2s/次
,那麼
函式防抖如圖
因為2s/次
已經大於了規定的最小時間,所以每計時兩秒便觸發一次。
函式節流如圖
同樣,2s/次
大於了最小時間規定,所以每一次觸發都生效。
應用場景
對於函式防抖,有以下幾種應用場景:
- 給按鈕加函式防抖防止表單多次提交。
- 對於輸入框連續輸入進行AJAX驗證時,用函式防抖能有效減少請求次數。
- 判斷
scroll
是否滑到底部,滾動事件
+函式防抖
總的來說,適合多次事件一次響應的情況
對於函式節流,有如下幾個場景:
- 遊戲中的重新整理率
- DOM元素拖拽
- Canvas畫筆功能
總的來說,適合大量事件按時間做平均分配觸發。
原始碼
函式防抖:
function debounce(fn, wait) {
var timer = null;
return function () {
var context = this
var args = arguments
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(function () {
fn.apply(context, args)
}, wait)
}
}
var fn = function () {
console.log('boom')
}
setInterval(debounce(fn,500),1000) // 第一次在1500ms後觸發,之後每1000ms觸發一次
setInterval(debounce(fn,2000),1000) // 不會觸發一次(我把函式防抖看出技能讀條,如果讀條沒完成就用技能,便會失敗而且重新讀條)
複製程式碼
之所以返回一個函式,因為防抖本身更像是一個函式修飾,所以就做了一次函式柯里化。裡面也用到了閉包,閉包的變數是timer
。
函式節流
function throttle(fn, gapTime) {
let _lastTime = null;
return function () {
let _nowTime = + new Date()
if (_nowTime - _lastTime > gapTime || !_lastTime) {
fn();
_lastTime = _nowTime
}
}
}
let fn = ()=>{
console.log('boom')
}
setInterval(throttle(fn,1000),10)
複製程式碼
如圖是實現的一個簡單的函式節流,結果是一秒打出一次boom
小結
函式防抖和函式節流是在時間軸上控制函式的執行次數。防抖可以類比為電梯不斷上乘客
,節流可以看做幻燈片限制頻率播放電影
。