函數防抖與函數節流

only_lzw 7年前發布 | 10K 次閱讀 JavaScript開發 JavaScript

區別

debounce(防抖):當調用動作n毫秒后,才會執行該動作,若在這n毫秒內又調用此動作則將重新計算執行時間。比如:如果用手指一直按住一個彈簧,它將不會彈起直到你松手為止。

throttle(節流):預先設定一個執行周期,當調用動作的時刻大于等于執行周期則執行該動作,然后進入下一個新周期。比如:將水龍頭擰緊直到水是以水滴的形式流出,那你會發現每隔一段時間,就會有一滴水流出。

適用情景

  • window對象的resize、scroll事件

  • 拖拽時的mousemove事件

  • 射擊游戲中的mousedown、keydown事件

  • 文字輸入、自動完成的keyup事件

實際上對于window的resize事件,實際需求大多為停止改變大小n毫秒后執行后續處理 (防抖);而其他事件大多的需求是以一定的頻率執行后續處理(節流)。

增加一個輔助函數 restArgs

/**

 * 類ES6 rest參數的實現,使某個函數具備支持rest參數的能力
 * @param func 需要rest參數的函數
 * @param startIndex 從哪里開始標識rest參數, 如果不傳遞, 默認最后一個參數為rest參數
 * @returns {Function} 返回一個具有rest參數的函數
 */
var restArgs = function (func, startIndex) {
    // rest參數從哪里開始,如果沒有,則默認視函數最后一個參數為rest參數
    // 注意, 函數對象的length屬性, 揭示了函數的參數個數
    /*
     ex: function add(a,b) {return a+b;}
     console.log(add.length;) // 2
     */r
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    // 返回一個支持rest參數的函數
    return function () {
        // 校正參數, 以免出現負值情況
        var length = Math.max(arguments.length - startIndex, 0);
        // 為rest參數開辟數組存放
        var rest = Array(length);
        // 假設參數從2個開始: func(a,b,*rest)
        // 調用: func(1,2,3,4,5); 實際的調用是:func.call(this, 1,2, [3,4,5]);
        for (var index = 0; index < length; index++) {
            rest[index] = arguments[index + startIndex];
        }
        // 根據rest參數不同, 分情況調用函數, 需要注意的是, rest參數總是最后一個參數, 否則會有歧義
        switch (startIndex) {
            case 0:
                // call的參數一個個傳
                return func.call(this, rest);
            case 1:
                return func.call(this, arguments[0], rest);
            case 2:
                return func.call(this, arguments[0], arguments[1], rest);
        }
        // 如果不是上面三種情況, 而是更通用的(應該是作者寫著寫著發現這個switch case可能越寫越長, 就用了apply)
        var args = Array(startIndex + 1);
        // 先拿到前面參數
        for (index = 0; index < startIndex; index++) {
            args[index] = arguments[index];
        }
        // 拼接上剩余參數
        args[startIndex] = rest;
        return func.apply(this, args);
    };
};</code></pre> 

debounce

返回 function 函數的防反跳版本, 將延遲函數的執行(真正的執行)在函數最后一次調用時刻的 wait 毫秒之后. 對于必須在一些輸入(多是一些用戶操作)停止到達之后執行的行為有幫助。 例如: 渲染一個Markdown格式的評論預覽, 當窗口停止改變大小之后重新計算布局, 等等.

傳參 immediate 為 true, debounce會在 wait 時間間隔的開始調用這個函數 。在類似不小心點了提交按鈕兩下而提交了兩次的情況下很有用。

var debounce = function (func, wait, immediate) {
        var timeout, result;

    var later = function (context, args) {
        timeout = null;
        if (args) result = func.apply(context, args);
    };

    var debounced = restArgs(function (args) {
        // 一旦存在timeout, 意味之前嘗試調用過func
        // 由于debounce只認最新的一次調用, 所以之前等待執行的func都會被終止
        if (timeout) clearTimeout(timeout);
        // 如果允許新的調用嘗試立即執行,
        if (immediate) {
            // 如果之前尚沒有調用嘗試,那么此次調用可以立馬執行,否則一定得等待之前的執行完畢
            var callNow = !timeout;
            // 刷新timeout
            timeout = setTimeout(later, wait);
            // 如果能被立即執行,立即執行
            if (callNow) result = func.apply(this, args);
        } else {
            // 否則,這次嘗試調用會延時wait個時間
            timeout = delay(later, wait, this, args);
        }

        return result;
    });

    debounced.cancel = function () {
        clearTimeout(timeout);
        timeout = null;
    };

    return debounced;
};</code></pre> 

throttle

創建并返回一個像節流閥一樣的函數,當重復調用函數的時候,至少每隔 wait毫秒調用一次該函數。對于想控制一些觸發頻率較高的事件有幫助。(愚人碼頭注:詳見:javascript函數的throttle和debounce,感謝 @澳利澳先生 的翻譯建議)

默認情況下,throttle將在你調用的第一時間盡快執行這個function,并且,如果你在wait周期內調用任意次數的函數,都將盡快的被覆蓋。如果你想禁用第一次首先執行的話,傳遞{leading: false},還有如果你想禁用最后一次執行的話,傳遞{trailing: false}。

var throttle = function (func, wait, options) {

    var timeout, context, args, result;
    // 最近一次func被調用的時間點
    var previous = 0;
    if (!options) options = {};

    // 創建一個延后執行的函數包裹住func的執行過程
    var later = function () {
        // 執行時,刷新最近一次調用時間
        previous = options.leading === false ? 0 : new Date();
        // 清空定時器
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
    };

    // 返回一個throttled的函數
    var throttled = function () {
        // ----- 節流函數開始執行----
        // 我們嘗試調用func時,會首先記錄當前時間戳
        var now = new Date();
        // 是否是第一次調用
        if (!previous && options.leading === false) previous = now;
        // func還要等待多久才能被調用 =  預設的最小等待期-(當前時間-上一次調用的時間)
        // 顯然,如果第一次調用,且未設置options.leading = false,那么remaing=0,func會被立即執行
        var remaining = wait - (now - previous);
        // 記錄之后執行時需要的上下文和參數
        context = this;
        args = arguments;

        // 如果計算后能被立即執行
        if (remaining <= 0 || remaining > wait) {
            // 清除之前的“最新調用”
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            // 刷新最近一次func調用的時間點
            previous = now;
            // 執行func調用
            result = func.apply(context, args);
            // 如果timeout被清空了,
            if (!timeout) context = args = null;

        } else if (!timeout && options.trailing !== false) {
            // 如果設置了trailing edge,那么暫緩此次調用嘗試的執行
            timeout = setTimeout(later, remaining);
        }
        return result;
    };

    // 可以取消函數的節流化
    throttled.cancel = function () {
        clearTimeout(timeout);
        previous = 0;
        timeout = context = args = null;
    };

    return throttled;
};</code></pre> 

參考文章

http://www.tuicool.com/articl...

http://blog.csdn.net/jinboker...

http://www.css88.com/doc/unde...

 

來自:https://segmentfault.com/a/1190000008275548

 

 本文由用戶 only_lzw 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!