1. 程式人生 > >JavaScript函數節流(throttle)與函數去抖(debounce)

JavaScript函數節流(throttle)與函數去抖(debounce)

apply 時間 gpo ava size method handler 如果 兩種方法

對於瀏覽器窗口大小改變的時候,來動態改變頁面元素的大小,可以采用window的resize事件,實現代碼:

<script type="text/javascript">
    var n = 0;
    function resizehandler(){
        console.log(new Date().getTime());
        console.log(++n);
    }
    
    window.onresize = resizehandler;
</script>

功能能夠實現,都是當我們用拖拽的方式改變瀏覽器大小的時候,控制臺會不斷打印執行resizehandler的函數的結果。

一次簡單的拖拽會讓resizehandler()函數執行很多次,實際在顯示項目中resizehandler函數可能會很復雜,甚至會涉及到前後端的數據交互,所以一次拖拽執行很多次很明顯是不能夠接受的。

函數去抖

其實我們的本意只是窗口resize後頁面做一些調整就可以了,而window的resize事件並不是在resize結束後才出發,具體的觸發頻率不是很清楚,但卻在不停地調用,直到窗口大小不在變化。類似的機制還有鼠標的mousemove,都是在短時間內重復觸發。

在《JavaScript高級程序設計》中有專門應對此問題的函數防抖

function throttle(method, context){
    clearTimeout(method.tId);
    method.tId 
= setTimeout(function(){ method.call(context); }, 500); }

原理很簡單,利用定時器,讓函數執行延遲500毫秒,在500毫秒內如果有函數又被調用則刪除上一次的調用,這次調用500毫秒後執行,如此往復。這樣剛才的代碼可以改為:

<script type="text/javascript">
    var n = 0;
    function resizehandler(){
        console.log(new Date().getTime());
        console.log(++n);
    }
    
    
function throttle(method, context){ clearTimeout(method.tId); method.tId = setTimeout(function(){ method.call(context); }, 500); } window.onresize = function(){ throttle(resizehandler, window); }; </script>

這樣的話執行就沒有問題了。

函數防抖的另一種方法

預先設定一個執行周期,當調用動作的時刻大於等於執行周期則執行該動作,然後進入下一個周期。

function throttle(method, dalay){
    var timer = null;
    return function(){
        var context = this, args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function(){
            method.apply(context, args);
        }, delay);
    }
}

調用一下試試,一樣的效果

<script type="text/javascript">
    var n = 0;
    function resizehandler(){
        console.log(new Date().getTime());
        console.log(++n);
    }
    
    function throttle(method, delay){
        var timer = null;
        return function(){
            var context = this, args = arguments;
            clearTimeout(timer);
            timer = setTimeout(function(){
                method.apply(context, args);
            }, delay);
        }
    }
    
    window.onresize = throttle(resizehandler, 500); //這裏因為返回函數句柄,不用包裝函數了。
</script>

比較

兩種方法都是利用了setTimeout,不同的是第二種方法加入的函數延遲執行時間,這個在第一種方案中很容易也具有的功能,無非是加一個參數。

但是第一種方案把tId設為函數的一個變量保存,而第二種創建了閉包存儲。個人覺得差距不大,很喜歡第一種,簡單,高效。

新需求

百度首頁輸入自動提示一樣的東西,我在text上綁定keyup事件,每次鍵盤彈起的時候自動提示,但是又不想提示那麽頻繁,於是我用了上面方法,但是悲劇了,只有挺直輸入等500毫秒才會提示,在輸入過程中根本就沒有提示。看了一下代碼,可不是嘛,只有用戶會盲打,在500毫秒內按一下鍵盤,提示函數就會不斷被延遲,這樣只有停下來的時候才會提示,這就沒有意義了。

能不能在函數節流的基礎上間隔固定時間就執行一次?

函數節流

在網上搜了一下我們可以根據第二種下發(第一種函數拓展多個變量感覺有些不好)做些改動,添加一個參數作為到固定間隔必須執行。

function throttle(method, delay, duration){
    var timer = null, begin = new Date();
    return function(){
        var context = this, args = arguments, current = new Date();
        clearTimeout(timer);
        if(current-begin >= duration){
            method.apply(context, args);
            begin = current;
        } else {
            timer = setTimeout(function(){
                method.apply(context, args);
            }, delay);
        }
    }
}

這樣每次我們判斷間隔了夠久,要是超過設置時間則立即執行一次,以剛才的例子試一試效果

window.onresize = throttle(resizehandler, 100, 200);

這樣,既沒有頻繁執行也沒有就最後執行。

總結

對於函數節流在做動態響應用戶行為方面有較大的使用頻率,具體使用基礎版本的函數節流還是改動版本的還要根據業務場景進行具體分析。

throttle和debounce均是通過減少實際邏輯處理過程的執行來提高事件處理函數運行性能的手段,並沒有實質上減少事件的觸發次數。 (逃)

JavaScript函數節流(throttle)與函數去抖(debounce)