underscore debounce函數分析
標籤(空格分隔): underscore
本文是underscore源碼剖析系列第六篇文章,上節我們介紹了throttle節流函數的實現,這節將會介紹一下節流函數的兄弟 —— debounce防抖動函數。
throttle函數是在高頻率觸發的情況下,為了防止函數的頻繁調用,將其限制在一段時間內只會調用一次。而debounce函數則是在頻繁觸發的情況下,只在觸發的最後一次調用一次,想像一下如果我們用手按住一個彈簧,那麼只有等到我們把手鬆開,彈簧才會彈起來,下面我用一個電梯的例子來介紹debounce函數。
電梯
假如我下班的時候去坐電梯,等了一段時間後,電梯正準備關上門下降,這個時候一個同事走了過來,電梯門被打開,這樣電梯就會繼續等一段時間,如果中間一直有人進來,那麼電梯就一直不會下降,直到最後一個人進來後過了一定時間後還沒有下一個人進來,這時電梯才會下降。
應用場景
除了電梯,事實上我們還有很多應用場景,比如我用鍵盤不斷輸入文字,我希望等最後一次輸入結束後才會調用接口來請求展示聯想詞,如果每次輸入一個字的時候就會調用接口,這樣調用未免太過於頻繁了。
沒有debounce時:
有debounce時:
簡單的debounce
知道debounce的工作原理了,我們可以先自己實現一個比較簡單的debounce函數。
function debounce(func, wait) {
var timeout,
args,
context
var later = function() {
func.apply(context, args)
timeout = context = args = null
}
return function() {
context = this
args = arguments
// 每次觸發都清理掉前一次的定時器
clearTimeout(timeout)
// 只有最後一次觸發後才會調用later
timeout = setTimeout(later, wait)
}
}
麻雀雖小,五臟俱全,不過這個函數還是有很多問題,比如每次觸發都設置了太多的setTimeout,這樣會比較耗費cpu,我們來看一下underscore的實現方式。
underscore debounce
// debounce函數傳入三個參數,分別是要執行的函數func,延遲時間wait,是否立即執行immediate
// 如果immediate為true,那麼就會在wait時間段一開始就執行一次func,之後不管觸發多少次都不會再執行func
// 在類似不小心點了提交按鈕兩下而提交了兩次的情況下很有用
_.debounce = function (func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function () {
// 這個是最關鍵的一步,因為每次觸發的時候都要記錄當前timestamp
// 但是later是第一次觸發後wait時間後執行的,_now()減去第一次觸發時的時間當然是等於wait的
// 但是如果後續繼續觸發,那麼_.now() - timestamp肯定會小於wait
// last是執行later的時間和上一次觸發的時間差
var last = _.now() - timestamp;
// 如果在later執行前還有其他觸發,那麼就會重新設置定時器
// last >= 0應該是防止客户端系統時間被調整
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
// 如果last大於等於wait,也就是説設置timeout定時器後沒有再觸發過
} else {
timeout = null;
// 這個時候如果immediate不為true,就會立即執行func函數,這也是為什麼immediate為true的時候只會執行第一次觸發
if (!immediate) {
result = func.apply(context, args);
// 解除引用
if (!timeout) context = args = null;
}
}
};
return function () {
context = this;
args = arguments;
// 每次觸發都用timestamp記錄時間戳
timestamp = _.now();
// 第一次進來的時候,如果immediate為true,那麼會立即執行func
var callNow = immediate && !timeout;
// 第一次進來的時候會設置一個定時器
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
};
為了防止出現我們上面那種不停地設置定時器的情況,underscore只在later函數中在上一次定時器執行結束後才重新設置定時器。
如果傳入的immediate為true,那麼只會在第一次進來的時候立即執行。很明顯在上面代碼中func執行只有兩處,一個是callNow判斷裏面,一個是!immediate判斷裏面,所以這樣保證了後續觸發不會再執行func。
參考鏈接:
1、淺談throttle以及debounce的原理和實現