博客 / 詳情

返回

underscore debounce函數分析

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的原理和實現

user avatar laughingzhu 頭像 lanlanjintianhenhappy 頭像 jidongdehai_co4lxh 頭像 dujing_5b7edb9db0b1c 頭像 shaochuancs 頭像 sunhengzhe 頭像 buxia97 頭像 weirdo_5f6c401c6cc86 頭像 suporka 頭像 yilezhiming 頭像 liyl1993 頭像 joytime 頭像
48 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.