【DG】手寫實現防抖&節流

使用目的:

解決當函數在某些場景下被無限制的頻繁調用,會增長瀏覽器的負擔,會形成卡頓的現象瀏覽器

使用場景:

鼠標滾動、鍵盤輸入操做、窗口resize等等閉包

原理:

防抖:事件持續觸發時,若是間隔時間小於預設的時間,不會執行函數,同時以最後觸發事件的時間爲準從新計時。總之,必定要在事件觸發完的預設時間內沒有觸發過,纔會執行函數app

手寫+逐步完善函數

第一個實現版本(注意執行上下文)

function _debounce(func, wait){
    // 定義一個計時器
    var timer
    return function(){
        // 拿到當前的執行上下文
        var context = this
        clearTimeout(timer)
        timer = setTimeout(function(){
            // 由於setTimeout回調方法中this永遠是window,因此~
            func.apply(context)
        },wait)
    }
    
}

以上實現方式有一個不足,就是當事件處理函數傳入參數(event對象)時,沒有作到獲取,修改一下:優化

第二個實現版本(注意處理傳參)

function _debounce(func, wait){
    var timer
    return function(){
        var context = this
        // 經過arguments拿到調用時所傳入的參數
        var arg = arguments
        clearTimeout(timer)
        timer = setTimeout(function(){
            // 執行時傳入參數
            func.apply(context, arg)
        },wait)
    }
    
}

此時還有一個問題,最開始函數必需要在事件初次觸發後,等待wait以後纔會執行,咱們想要用一次當即執行來兜底初次觸發:this

第三個實現版本(處理初始當即執行)

function _debounce(func, wait, immediate){
    var timer
    return function(){
        var context = this
        var arg = arguments
        if(timer) clearTimeout(timer)
        if(immediate){
            /** timer爲true,callNow爲false期間
             *  都表明不是初次觸發
             *  timer初始布爾值爲false
             *  初次執行以後timer在wait時間內一直爲true
            **/
            var callNow = !timer
            // 這一句在執行完以前 timer都爲true
            timer = setTimeout(function(){
                timer = null
            },wait)
            if(callNow) func.apply(context,arg)
        }else{
            timer = setTimeout(function(){
                func.apply(context, arg)
            },wait)
        }
    }  
}
// 中心思想就是有一個標識,用來標記當前是否爲初次觸發,用以判斷是否須要執行,而在執行後wait時間後將標識變爲初始狀態

**特別版本:防抖函數有可能會形成一直不觸發的現象code

function throttle(fn, delay){
  /*last爲上一次觸發回調的時間, timer是定時器*/
  var last = 0, timer = null
  
  return function () { 
    let context = this
    let args = arguments
    let now = +new Date()
    
     /*判斷上次觸發的時間和本次觸發的時間差是否小於時間間隔的閾值*/
    if (now - last < delay) {
    /* 若是時間間隔小於咱們設定的時間間隔閾值,
     * 則爲本次觸發操做設立一個新的定時器
     * 其實這裏就是本來正常的防抖邏輯
     */
       clearTimeout(timer)
       timer = setTimeout(function () {
          last = now
          fn.apply(context, args)
        }, delay)
    } else {
        /* 若是時間間隔超出了咱們設定的時間間隔閾值,那就不等了,不管如何要反饋給用戶一次響應*/
        last = now
        fn.apply(context, args)
    }
  }
}

還能夠有一些優化,好比有返回值時等等,暫時到這裏 最好能記牢對象


節流:事件持續觸發期間,每間隔預設的時間纔會執行一次事件

手寫+逐步完善(定時器 or 時間戳)
一、時間戳版本rem

function _throttle(func, wait){
    var timer = 0
    return function(){
        var arg = arguments
        var now = new Date()
        if(now - timer >= wait){
            func.apply(this,arg)
            timer = now
        }
    }
}
/** 這裏懵逼了,理解一下應該是將throttle執行後的結果,
 *  也就是閉包函數賦值給了container的mousemove
 *  以後每次mousemove執行的都是閉包函數
**/
container.onmousemove = throttle(xxx,1000)

二、定時器版本

function _throttle(func,wait){
    var timer
    return function(){
        var arg = arguments
        if(!timer){
            timer = setTimeout(function(){
                timer = null
                func.apply(this,arg)
            },wait)
        }
    }
}

以上兩種實現方式,時間戳版本會當即執行,但不會有最後一一次執行;定時器版本不會當即執行,但會有最後一次操做執行兜底
最好是將這兩種狀況合二爲一,更加優秀

三、取長補短版本:

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;
}
// 這個難 暫時先理解吧
相關文章
相關標籤/搜索