函數防抖與函數節流

區別

debounce(防抖):當調用動做n毫秒後,纔會執行該動做,若在這n毫秒內又調用此動做則將從新計算執行時間。好比:若是用手指一直按住一個彈簧,它將不會彈起直到你鬆手爲止。css

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

適用情景

  • window對象的resize、scroll事件app

  • 拖拽時的mousemove事件函數

  • 射擊遊戲中的mousedown、keydown事件佈局

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

實際上對於window的resize事件,實際需求大多爲中止改變大小n毫秒後執行後續處理 (防抖);而其餘事件大多的需求是以必定的頻率執行後續處理(節流)。this

增長一個輔助函數 restArgs.net

/**
     * 類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);
        };
    };

debounce

返回 function 函數的防反跳版本, 將延遲函數的執行(真正的執行)在函數最後一次調用時刻的 wait 毫秒以後. 對於必須在一些輸入(可能是一些用戶操做)中止到達以後執行的行爲有幫助。 例如: 渲染一個Markdown格式的評論預覽, 當窗口中止改變大小以後從新計算佈局, 等等.rest

傳參 immediate 爲 true, debounce會在 wait 時間間隔的開始調用這個函數 。在相似不當心點了提交按鈕兩下而提交了兩次的狀況下頗有用。code

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;
    };

throttle

建立並返回一個像節流閥同樣的函數,當重複調用函數的時候,至少每隔 wait毫秒調用一次該函數。對於想控制一些觸發頻率較高的事件有幫助。

默認狀況下,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;
    };

點擊查看demo

參考文章
http://www.tuicool.com/articl...
http://blog.csdn.net/jinboker...
http://www.css88.com/doc/unde...

相關文章
相關標籤/搜索