節流函式throttle是什麼鬼?

程式設計師大白發表於2018-08-21

throttle是什麼鬼?

throttle的本意是節流閥,是一種通過改變節流截面或者節流長度來控制液體流量的裝置。而在前端開發中,throttle一般用來控制在某段時間間隔內耗時操作只被觸發一次。比如說有這樣的場景,有個列表,沒有所謂的提交操作,使用者在點選列表的某一項之後需要通過ajax將使用者當前點選的項傳給後臺進行相應處理,很明顯,ajax是個耗時的操作,如果我們直接設定使用者點選列表的某項後直接發ajax的話那麼很有可能會在短時間內傳送大量的ajax(沒法排除某些使用者玩心大起,突然就想快速點選很多列表項,比如說我這種人,O(∩_∩)O哈哈~)

不同的throttle實現

直接用setTimeout控制函式執行次數(實現簡單粗暴,很容易理解)

function throttle(func, interval = 500) {
    let timer;

    return function () {
        const that = this,
            args = arguments;

        if (timer) {
            return false;
        }

        timer = setTimeout(function () {
            func.apply(that, args);
            clearTimeout(timer);
            timer = null;
        }, interval);
    }
}

// 控制每300ms列印日誌的操作最多被執行一次
window.onresize = throttle(function () {
    console.log('hello world');
}, 300);
複製程式碼

首次觸發直接執行,不用等待指定時間再執行(和第一種基本上差不多,就是加了個firstTime用來區別是否首次執行而已)

function throttle(func, interval = 500) {
    let timer,
        firstTime = true;

    return function () {
        const that = this,
            args = arguments;

        if (firstTime) {
            firstTime = false;
            return func.apply(that, args);
        }

        if (timer) {
            return false;
        }

        timer = setTimeout(function () {
            func.apply(that, args);
            clearTimeout(timer);
            timer = null;
        }, interval);
    }
}

// 控制每300ms列印日誌的操作最多被執行一次,首次觸發馬上執行
window.onresize = throttle(function () {
    console.log('hello world');
}, 300);
複製程式碼

上面兩種實現基本上能夠滿足我們大部分需求了,那如果我並不想最後一次被執行怎麼辦?比如說我們為keyup事件繫結第二個版本(首次立即執行)的throttle,然後我總共就按下了兩個字元,那麼按照第二種實現方式在我按完第二個字元interval時間之後函式又會再觸發一次,這可能並不是我們想要的。那怎麼修改throttle才能夠實現這種效果呢?且看throttle的第三個版本。

function throttle(func, interval = 500) {
    let timer,
        firstTime = true,
        previousExecuteTime,
        now;

    return function () {
        now = Date.now();

        const that = this,
            args = arguments;

        if (firstTime) {
            firstTime = false;
            previousExecuteTime = Date.now();
            return func.apply(that, args);
        }

        if (timer) {
            return false;
        }

        timer = setTimeout(function () {
            if (now - previousExecuteTime >= interval) {
                func.apply(that, args);
                clearTimeout(timer);
                timer = null;

                previousExecuteTime = now;
            } else {
                firstTime = true;
                timer = null;
            }
        }, interval);
    }
}

// 使用者首次調整視窗時列印日誌,若從上次執行到調整視窗結束時間不到interval時間則不觸發函式執行
window.onresize = throttle(function () {
    console.log('hello world');
}, 2000);
複製程式碼

從第三種實現方式來看,第二種實現方式其實有個問題,那就是firstTime標誌在執行過一次之後永遠都是false,這樣的話再次觸發事件其實也就沒有首次觸發執行功能了。所以綜合來看還是第三種實現方式好點,如果想讓最後一次操作被執行的話也可以給第三種實現方式加個引數leading,如果為true的話就執行,否則不執行

if (leading || (now - previousExecuteTime >= interval))
複製程式碼

結束語

從開始決定每週輸出一篇技術文章到現在也有挺長時間了,個人覺得主要有以下兩方面的收穫:一方面是對於自己真正花時間去研究,去用輸出倒逼輸入的技術有了更深一步的認識,使用起來也更加得心應手;另一方面有時候自己寫的文章得到別人的喜歡,點贊覺得很開心,想要持續學習,繼續輸出高質量的文章和大家一起進步。

相關文章