underscore源碼解讀之debounce

剛寫完一篇debounce(防抖)函數的實現,我又看了下underscore.js的實現方法。算是趁熱打鐵,分析一下underscore裏實現的套路。app

先貼上源碼:函數

_.debounce = function(func, wait, immediate) {
    var timeout, args, context, timestamp, result;

    var later = function() {
        var last = _.now() - timestamp;
        console.log(last)
        if (last < wait && last >= 0) {
            console.log(1)
            timeout = setTimeout(later, wait - last);
        } else {
            console.log(2)
            timeout = null;
            if (!immediate) {
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            }
        }
    };

    return function() {
        context = this;
        args = arguments;
        timestamp = _.now();
        var callNow = immediate && !timeout;
        console.log(timeout)
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
            result = func.apply(context, args);
            context = args = null;
        }

        return result;
    };
};

一看可能有點多,我簡化一下,總體其實就兩部分:this

_.debounce = function( func, wait, immediate){

    // 函數的回調部分
    // 當immediate === false時
    // func真正的執行部分
    function later(){};
    
    return function(){
        // 在這裏判斷func是否當即執行
        // 是否有計時器的存在
    }
}

上一篇文章已經分析過this的指向和event的傳遞,這裏就很少說了。直接來分析返回的匿名函數部分。code

return function() {
        context = this;
        args = arguments;
        // 這裏調用了underscore封裝的調用時間戳的方法
        // 等同於
        // timestamp = Date.now()
        timestamp = _.now();
        var callNow = immediate && !timeout;
        console.log(timeout)
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
            result = func.apply(context, args);
            context = args = null;
        }
    };

這裏我要說的是timestamp,它存儲的是動做發生時的時間戳,假設我這裏調用debounce時傳入的wait爲10000,也就是10秒。我第一次調用事件函數是在10:00:00,按照設定,10:00:10以後才能調用第二次方法,在這10秒內,任何調用都是不執行的。事件

當我第一次執行事件時underscore

timeout = undefined;
immediate先設置爲false

因此回調函數

callNow === false

只有這句話是執行的源碼

if (!timeout) timeout = setTimeout(later, wait);

那接着來看later都有什麼:it

var later = function() {
  //     var last = Date.now() - timestamp;
    var last = _.now() - timestamp;
    console.log(last)
    if (last < wait && last >= 0) {
        console.log(1)
        timeout = setTimeout(later, wait - last);
    } else {
        console.log(2)
        timeout = null;
        if (!immediate) {
            result = func.apply(context, args);
            if (!timeout) context = args = null;
        }
    }
};

在上一篇中,判斷wait內重複輸入,咱們取消事件的方法是這樣的io

if(timer){clearTimeout(timer)}

但在這裏,咱們是否是都還沒看到怎麼處理wait時間內,重複輸入無效的問題?別急,如今就來講。玄妙都在這個last變量上。

以前說過,timestamp存儲的是第一次事件執行時的時間戳(10:00:00),但如今我沒想等十秒,過了五秒我就觸發了第二次事件。因此timestamp如今的內容就變成新的時間戳了(10:00:05)。但問題是,timer的回調函數至少要到10:00:10以後纔會執行,也就是說

last>=5

因爲代碼執行堵塞致使last>10的狀況有可能存在,可是不符合咱們如今討論的,並且真的是太特殊了,咱們就不說了。那就假設last爲5秒(5000ms)。

last < wait && last >= 0

這句話就是true,那就執行裏面的代碼。但注意看裏面計時器對於時間的寫法。

wait - last

換個說法就是,你在10:00:00啓動了我,可是你在10:00:05又動了,我本來應該在10:00:10執行,可是如今懲罰你提早行動,那你以前等的時間就不算,你要再從新多等這幾秒10:00:15。

這個難點解決了,其餘就都好說。

lster剩餘的部分就是判斷若是當初設置的是當即執行(immediate = true),func就再也不執行一遍了,不然(immediate = false)func執行。

恩,那這個的解讀就結束了,有什麼地方我沒寫清楚的話,請給我留言。

相關文章
相關標籤/搜索