JavaScript專題之跟着 underscore 學節流

JavaScript 專題系列第二篇,講解節流,帶你從零實現一個 underscore 的 throttle 函數git

前言

《JavaScript專題之跟着underscore學防抖》中,咱們瞭解了爲何要限制事件的頻繁觸發,以及如何作限制:github

  1. debounce 防抖app

  2. throttle 節流函數

今天重點講講節流的實現。優化

節流

節流的原理很簡單:this

若是你持續觸發事件,每隔一段時間,只執行一次事件。spa

根據首次是否執行以及結束後是否執行,效果有所不一樣,實現的方式也有所不一樣。
咱們用 leading 表明首次是否執行,trailing 表明結束後是否再執行一次。3d

關於節流的實現,有兩種主流的實現方式,一種是使用時間戳,一種是設置定時器。code

使用時間戳

讓咱們來看第一種方法:使用時間戳,當觸發事件的時候,咱們取出當前的時間戳,而後減去以前的時間戳(最一開始值設爲 0 ),若是大於設置的時間週期,就執行函數,而後更新時間戳爲當前的時間戳,若是小於,就不執行。blog

看了這個表述,是否是感受已經能夠寫出代碼了…… 讓咱們來寫初版的代碼:

// 初版
function throttle(func, wait) {
    var context, args;
    var previous = 0;

    return function() {
        var now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

例子依然是用講 debounce 中的例子,若是你要使用:

container.onmousemove = throttle(getUserAction, 1000);

效果演示以下:

使用時間戳

咱們能夠看到:當鼠標移入的時候,事件馬上執行,每過 1s 會執行一次,若是在 4.2s 中止觸發,之後不會再執行事件。

使用定時器

接下來,咱們講講第二種實現方式,使用定時器。

當觸發事件的時候,咱們設置一個定時器,再觸發事件的時候,若是定時器存在,就不執行,直到定時器執行,而後執行函數,清空定時器,這樣就能夠設置下個定時器。

// 第二版
function throttle(func, wait) {
    var timeout;
    var previous = 0;

    return function() {
        context = this;
        args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

爲了讓效果更加明顯,咱們設置 wait 的時間爲 3s,效果演示以下:

使用定時器

咱們能夠看到:當鼠標移入的時候,事件不會馬上執行,晃了 3s 後終於執行了一次,此後每 3s 執行一次,當數字顯示爲 3 的時候,馬上移出鼠標,至關於大約 9.2s 的時候中止觸發,可是依然會在第 12s 的時候執行一次事件。

因此比較兩個方法:

  1. 第一種事件會馬上執行,第二種事件會在 n 秒後第一次執行

  2. 第一種事件中止觸發後沒有辦法再執行事件,第二種事件中止觸發後依然會再執行一次事件

雙劍合璧

那咱們想要一個什麼樣的呢?

有人就說了:我想要一個有頭有尾的!就是鼠標移入能馬上執行,中止觸發的時候還能再執行一次!

因此咱們綜合二者的優點,而後雙劍合璧,寫一版代碼:

// 第三版
function throttle(func, wait) {
    var timeout, context, args, result;
    var previous = 0;

    var later = function() {
        previous = +new Date();
        timeout = null;
        func.apply(context, args)
    };

    var throttled = function() {
        var now = +new Date();
        //下次觸發 func 剩餘的時間
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
         // 若是沒有剩餘的時間了或者你改了系統時間
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
        } else if (!timeout) {
            timeout = setTimeout(later, remaining);
        }
    };
    return throttled;
}

效果演示以下:

throttle3

咱們能夠看到:鼠標移入,事件馬上執行,晃了 3s,事件再一次執行,當數字變成 3 的時候,也就是 6s 後,咱們馬上移出鼠標,中止觸發事件,9s 的時候,依然會再執行一次事件。

優化

可是我有時也但願無頭有尾,或者有頭無尾,這個咋辦?

那咱們設置個 options 做爲第三個參數,而後根據傳的值判斷到底哪一種效果,咱們約定:

leading:false 表示禁用第一次執行
trailing: false 表示禁用中止觸發的回調

咱們來改一下代碼:

// 第四版
function throttle(func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        func.apply(context, args);
        if (!timeout) context = args = null;
    };

    var throttled = function() {
        var now = new Date().getTime();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
            timeout = setTimeout(later, remaining);
        }
    };
    return throttled;
}

取消

在 debounce 的實現中,咱們加了一個 cancel 方法,throttle 咱們也加個 cancel 方法:

// 第五版 非完整代碼,完整代碼請查看最後的演示代碼連接
...
throttled.cancel = function() {
    clearTimeout(timeout);
    previous = 0;
    timeout = null;
}
...

注意

咱們要注意 underscore 的實現中有這樣一個問題:

那就是 leading:falsetrailing: false 不能同時設置。

若是同時設置的話,好比當你將鼠標移出的時候,由於 trailing 設置爲 false,中止觸發的時候不會設置定時器,因此只要再過了設置的時間,再移入的話,就會馬上執行,就違反了 leading: false,bug 就出來了,因此,這個 throttle 只有三種用法:

container.onmousemove = throttle(getUserAction, 1000);
container.onmousemove = throttle(getUserAction, 1000, {
    leading: false
});
container.onmousemove = throttle(getUserAction, 1000, {
    trailing: false
});

至此咱們已經完整實現了一個 underscore 中的 throttle 函數,恭喜,撒花!

演示代碼

相關的代碼能夠在 Github 博客倉庫 中找到

專題系列

JavaScript專題系列目錄地址:https://github.com/mqyqingfeng/Blog

JavaScript專題系列預計寫二十篇左右,主要研究平常開發中一些功能點的實現,好比防抖、節流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特色是研(chao)究(xi) underscore 和 jQuery 的實現方式。

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。

相關文章
相關標籤/搜索