問題1:如果實作了dom拖拽功能,但是在綁定拖拽事件的時候發現每當元素稍微移動一點便觸發了大量的回調函數,導緻浏覽器直接卡死,這個時候怎麼辦?
問題2:如果給一個按鈕綁定了表單送出的post事件,但是使用者有些時候在網絡情況極差的情況下多次點選按鈕造成表單重複送出,如何防止多次送出的發生?
為了應對如上場景,便出現了 函數防抖 和 函數節流 兩個概念,總的來說:這兩個方法是在時間軸上控制函數的執行次數。
1、函數防抖(debounce)
概念: 在事件被觸發n秒後再執行回調,如果在這n秒内又被觸發,則重新計時。
生活中的執行個體: 如果有人進電梯(觸發事件),那電梯将在10秒鐘後出發(執行事件監聽器),這時如果又有人進電梯了(在10秒内再次觸發該事件),我們又得等10秒再出發(重新計時)。
2、函數節流(throttle)
概念: 規定一個機關時間,在這個機關時間内,隻能有一次觸發事件的回調函數執行,如果在同一個機關時間内某事件被觸發多次,隻有一次能生效。
生活中的執行個體: 我們知道目前的一種說法是當 1 秒内連續播放 24 張以上的圖檔時,在人眼的視覺中就會形成一個連貫的動畫,是以在電影的播放中基本是以每秒 24 張的速度播放的,為什麼不 100 張或更多?是因為 24 張就可以滿足人類視覺需求的時候,100 張就會顯得很浪費資源。
3、分析圖
假設,我們觀察的總時間為10秒鐘,規定1秒作為一次事件的最小間隔時間。
(1)如果觸發事件的頻率是 0.5s/次 ,那麼函數防抖,如圖:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5COhFWM0MWOzgjM4YzY0ETOwImY1I2NkNWN2QGM1YmYw8CX3AzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL1M3Lc9CX6MHc0RHaiojIsJye.png)
因為始終沒法等一秒鐘就被再次觸發了,是以最終沒有一次事件是成功的。
函數節流,如圖:
因為控制了最多一秒一次,頻率為 0.5s/次 ,是以每一秒鐘就有一次事件廢棄。最終控制成 1s/次。
(2)如果觸發事件的頻率是 2s/次 ,那麼
函數防抖,如圖:
因為 2s/次已經大于了規定的最小時間,是以每計時兩秒便觸發一次。
同樣, 2s/次 大于了最小時間規定,是以每一次觸發都生效。
4、應用場景
對于函數防抖,有以下幾種應用場景:
(1)給按鈕加函數防抖防止表單多次送出。
(2)對于輸入框連續輸入進行AJAX驗證時,用函數防抖能有效減少請求次數。
(3)判斷 scroll 是否滑到底部, 滾動事件 + 函數防抖
總的來說,适合多次事件,一次響應的情況
對于函數節流,有如下幾個場景:
(1)遊戲中的重新整理率
(2)DOM元素拖拽
(3)Canvas畫筆功能
總的來說,适合大量事件按時間做平均配置設定觸發。
5、實作源碼:
函數防抖:
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);