JS之防抖和節流

防抖和節流都是爲了避免窗口的resize、scroll、輸入框內容校驗等事件處理函數被頻繁調用,因此採用防抖和節流來限制事件調用的頻率

防抖

原理介紹

防抖是指當持續觸發事件時,一定時間段內沒有再次觸發事件,事件處理函數纔會執行一次,如果設定的時間段內又一次觸發了事件,就重新開始計時。

防抖在很多場景中都有應用:

  1. 輸入框中頻繁的輸入 內容時,間隔一段時間來檢驗用戶輸入的內容
  2. 頻繁點擊按鈕來觸發某個事件時
  3. 監聽瀏覽器的滾動事件
  4. 窗口的resize事件

舉個例子,比如淘寶購物時詢問客服,在交流過程中,客服會等待用戶一分鐘的時間(假設),如果在一分鐘內,用戶還會發消息(事件觸發),那麼客服就會回消息,然後繼續等待一分鐘,看看用戶是否還會發消息;如果等待時間超過一分鐘了,那麼就斷開和用戶的連接(執行響應函數)

防抖.png

在上圖中,事件觸發並不是立即執行響應函數,而是等待一段時間後纔會觸發響應函數;而且當是事件觸發多次時,等待時間會持續推遲到最後一個事件觸發,然後等待一段時間後執行響應函數。因此都是以最後一次事件觸發開始計時。

代碼實現及優化

自定義實現

實現思路:

  1. 當觸發一個函數時,不會立即執行該函數,而是等待一段時間後再執行(通過定時器來延遲函數的執行);
  2. 如果在等待時間段內,又重新觸發函數,那就取消上一次的函數執行(取消定時器);
  3. 如果在延遲時間內,沒有重新觸發函數,那麼這個函數將正常執行
//防抖函數的實現(fun爲需要處理的函數,wait爲延遲時間)
//默認情況下,fun函數的this是指向window的,但若fun函數需要用到返回函數中的arguments時,就需要改變this指針了
function debounce(fun, wait){
    let timer = null;
    return function(){
        if(timer) {clearTimeout(timer)};
        //獲取this和arguments
        let context = this;
        let args = arguments;
        timer = setTimeout(function(){
            //獲取對應節點對象的this和arguments
            fun.apply(context, args);
        },wait)
    }
}
//ES6新寫法,箭頭函數沒有this,會向外查找this
function debounce(fun, wait){
    let timer = null;
    return ()=>{
        if(timer) {clearTimeout(timer)};
        timer = setTimeout(function(){
            //獲取對應節點對象的this和arguments
            fun.apply(this, arguments);
        },wait)
    }
}

優化立即執行

若希望當第一次事件觸發時就立即執行響應事件函數,而不是等待一段時間,後續事件觸發時纔等待。可以使用flag來標記是否是第一次執行,若爲true,就按照第一次來執行,爲false就按照防抖來執行

//優化,flag是用戶上傳來判斷第一次是否立即執行
function debounce(fun,wait, flag){
    let timer = null;
    flag = flag || false;
    let handleFun = function(){
        if(timer){clearTimeout(timer);}
        let context = this;
        let args = arguments;
        if(flag){
            //設置一個變量來判斷是否立即執行
            let isExe = false;
            if(!timer){
                fun.apply(context, args);
                isExe = true;
            }
            
            timer = setTimeout(function(){
                timer = null;
                if(!isExe){
                    fun.apply(context, args);
                }
            },wait);
        }else{
            timer = setTimeout(function(){
            	fun.apply(context, args);    
            }.wait)
        }
    }
    return handleFun;
}

優化返回值

若想讓fun函數執行後有返回值,但是fun函數是發生在setTimeout函數中(異步執行的),所以通過return無法獲取返回值

異步操作可以通過promise以及回調函數來獲取返回值

//promise函數實現返回值
function debounce(fun, wait){
    let timer = null;
    return function(){
        return new Promise((resolve, reject)=>{
            if(timer) {clearTimeout(timer);}
            let context = this;
            let args = arguments;
            timer = setTimeout(function(){
                resolve(fun.apply(context, args));
            },wait);
        })
    }
}
//回調函數實現
function debounce(fun, wait, result){
    let timer = null;
    result = result || null; //通過result將結果回調出去
    return function(){
        if(timer){clearTimeout(timer);}
        let context = this;
        let args = arguments;
        timer = setTimeout(function(){
            let res = fun.apply(context, args);
            if(result){
                result(res);
            }
        },wait);
    }
}

節流

原理介紹

當持續觸發事件時,保證一定時間內只調用一次事件處理函數。

比如飛機大戰遊戲,當我們按下空格鍵時會發射一顆子彈,但是當按下空格鍵頻率很快時,子彈也會保持一定的頻率來發射,而不是按一次就發射一顆子彈。按下空格的操作在一定時間段內觸發了10次,但是響應函數就觸發了一次

應用場景:

  1. 監聽頁面的滾動事件
  2. 鼠標移動事件
  3. 頻繁點擊按鈕
  4. 遊戲中的一些設計

節流.png

上圖中,等待時間是固定的,不管在等待時間段內觸發幾次事件,響應函數只觸發一次

代碼實現及優化

自定義實現

實現思路:採用時間戳的方式來實現

  1. 用previous來記錄上一次執行的時間
  2. 每次準備執行前,獲取當前的時間now
  3. 函數執行後,將now賦值給previous
function throttle(fun, wait){
    let previous = 0;
    return function(){
        let context = this;
        let args = arguments;
        let now = new Date().getTime();//獲取時間戳
        if(now - previous > wait){
            fun.apply(context, args);
            previous = now;
        }
    }
}
//ES6新寫法
function throttle(fun, wait){
    let previous = 0;
    return ()=>{
        let now = new Date().getTime();//獲取時間戳
        if(now - previous > wait){
            fun.apply(this, arguments);
            previous = now;
        }
    }
}

優化最後執行

通過上圖可以看出,默認情況下,節流函數的最後一次不會執行。若希望最後一次能夠執行,可以這樣實現:

//優化最後執行
function throttle(fun,wait){
    let previous = 0;
    let timer = null;
    return function(){
        let context = this;
        let args = arguments;
        let now = new Date().getTime();
        if(now - previous > wait){
            if(timer){
                clearTimeout(timer);
                timer = null;
            }
            fun.apply(context, args);
            previous = now;
        }else if(timer === null){ //只是最後一次
            timer = setTimeout(function(){
                timer = null;
                fun.apply(context,args);
            },wait)
        }
    }
}

兩者區別

防抖是將幾次操作合併爲一次進行;節流是在一定時間內只執行一次函數。

總而言之,防抖只是在最後一次事件後才觸發函數;節流是保證在規定時間內執行一次事件處理函數

參考鏈接: https://mp.weixin.qq.com/s/qyeRecCBBwa-Zf_V-KIRxA

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章