前端性能優化之防抖-debounce

這周接到一個需求-給輸入框作模糊匹配。這還不簡單,監聽input事件,取到輸入值去調接口不就好了? 然然後端小哥說不行,這個接口的數據量很是大,這種方式調用接口的頻率過高,並且用戶輸入時調用根本沒有必要,只要在用戶中止輸入的那一刻切調接口就好了。 唉?這個場景聽起來怎麼這麼像防抖呢?javascript

那到底什麼是防抖呢? 你們必定見過那種左右兩邊中間放廣告位的網站,在網頁滾動時,廣告位要保持在屏幕中間,就要不斷地去計算位置,若是不作限制,在視覺上廣告位就像在「抖」。防止這種狀況,就叫防抖了!前端

防抖的原理是什麼? 我一直以爲網上流傳的例子很是形象:當咱們在乘電梯時,若是這時有人過來,咱們會出於禮貌一直按着開門按鈕等待,等到這人進電梯了,剛準備關門時,發現又有人過來了!咱們又要重複以前的操做,若是電梯空間無限大的話,咱們就要一直等待了。。。固然人的耐心是有限的!因此咱們規定了一個時間,好比10秒,若是10秒都沒人來的話,就關電梯門。java

用專業術語歸納就是:在必定時間間隔內函數被觸發屢次,但只執行最後一次。後端

最簡易版的代碼實現:性能優化

function debounce(fn, delay) {
    let timer = null;

    return function() {
        const context = this;
        const args = arguments;

        if (timer) {
            clearTimeout(timer);
            timer = null;
        }

        timer = setTimeout(() => {
            fn.apply(context, args);
        }, delay);
    };
}
複製代碼

fn是要進行防抖的函數,delay是設定的延時,debounce返回一個匿名函數,造成閉包,內部維護了一個私有變量timer。咱們一直會觸發的是這個返回的匿名函數,定時器會返回一個Id值賦給timer,若是在delay時間間隔內,匿名函數再次被觸發,定時器都會被清除,而後從新開始計時。閉包

固然簡易版確定不能知足平常的需求,好比可能須要第一次當即執行的,因此要稍作改動:app

function debounce(fn, delay, immediate) {
    let timer = null;

    return function() {
        const context = this;
        const args = arguments;

        timer && clearTimeout(timer);

        if(immediate) {
            const doNow = !timer;

            timer = setTimeout(() => {
                timer = null;
            }, delay);

            doNow && fn.apply(context, args);
        }
        else {
            timer = setTimeout(() => {
                fn.apply(context, args);
            }, delay);
        }
    };
}
複製代碼

比起簡易版,多了個參數immediate來區分是否須要當即執行。其它與簡易版幾乎一致的邏輯,除了判斷當即執行的地方:異步

const doNow = !timer;

timer = setTimeout(() => {
    timer = null;
}, delay);

doNow && fn.apply(context, args);
複製代碼

doNow變量的值爲!timer,只有!timer爲true的狀況下,纔會執行fn函數。第一次執行時,timer的初始值爲null,因此會當即執行fn。接下來非第一次執行的狀況下,等待delay時間後才能再次觸發執行fn。 注意!與簡易版的區別,簡易版是必定時間屢次內觸發,執行最後一次。而當即執行版是不會執行最後一次的,須要再次觸發。前端性能

防抖的函數多是有返回值,咱們也要作兼容:函數

function debounce(fn, delay, immediate) {
    let timer = null;

    return function() {
        const context = this;
        const args = arguments;
        let result = undefined;

        timer && clearTimeout(timer);

        if (immediate) {
            const doNow = !timer;

            timer = setTimeout(() => {
                timer = null;
            }, delay);
            
            if (doNow) {
                result = fn.apply(context, args);
            } 
        }
        else {
            timer = setTimeout(() => {
                fn.apply(context, args);
            }, delay);
        }

        return result;
    };
}
複製代碼

可是這個實現方式有個缺點,由於除了第一次當即執行,其它狀況都是在定時器中執行的,也就是異步執行,返回值會是undefined。

考慮到異步,咱們也能夠返回Promise:

function debounce(fn, delay, immediate) {
    let timer = null;

    return function() {
        const context = this;
        const args = arguments;

        return new Promise((resolve, reject) => {
            timer && clearTimeout(timer);

            if (immediate) {
                const doNow = !timer;

                timer = setTimeout(() => {
                    timer = null;
                }, delay);

                doNow && resolve(fn.apply(context, args));
            }
            else {
                timer = setTimeout(() => {
                    resolve(fn.apply(context, args));
                }, delay);
            }
        });
    };
}
複製代碼

如此,只要fn被執行,那一定能夠拿到返回值!這也是防抖的終極版了!

下次聊聊防抖的兄弟-前端性能優化之節流-throttle

相關文章
相關標籤/搜索