JS中的函數節流throttle詳解和優化


JS中的函數節流throttle詳解和優化
在前端開發中,有時會為頁面綁定resize事件,或者為一個頁面元素綁定拖拽事件(mousemove),這種事件有一個特點,在一個正常的操作中,有可能在一個短的時間內觸發非常多次事件綁定程序。
DOM操作時很消耗性能的,如果你為這些事件綁定一些操作DOM節點的操作的話,那就會引發大量的計算,在用戶看來,頁面可能就一時間沒有響應,這個頁面一下子變卡了變慢了。在IE下,如果你綁定的resize事件進行較多DOM操作可能直接就崩潰了。

怎么解決?函數節流(throttle)就是一種辦法。

函數節流的原理
某些代碼不可以在沒有間斷的情況連續重復執行。
第一次調用函數,創建一個定時器,在指定的時間間隔之后運行代碼。
當第二次調用該函數時,它會清除前一次的定時器並設置另一個。
如果前一個定時器已經執行過了,這個操作就沒有任何意義。
然而,如果前一個定時器尚未執行,其實就是將其替換為一個新的定時器。
目的是只有在執行函數的請求停止了一段時間之后才執行。

簡單點說就是:當觸發一個事件時,先setTimout讓這個事件延遲一會再執行,如果在這個時間間隔內又觸發了事件,那我們就clear掉原來的定時器,再setTimeout一個新的定時器延遲一會執行。
實際使用過程中考慮到用戶體驗問題,會在這個基礎上做一些優化。

一個簡單實現的例子:
函數實現方式:

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

調用方法:

window.onresize = function(){
    throttle(myFunc);
}

---------------

閉包實現方式:

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

調用方法:

window.onresize = throttle(myFunc, 50);
-------------
上面的函數節流有個問題是:只要你在50ms內不間斷resize,myFunc就一次都不執行處理函數。
對上面的節流函數做拓展:

var throttleV2 = function(fn, delay, mustRunDelay){
     var timer = null;
     var t_start;
     return function(){
         var context = this, args = arguments, t_curr = +new Date();
         clearTimeout(timer);
         if(!t_start){
             t_start = t_curr;
         }
         if(t_curr - t_start >= mustRunDelay){
             fn.apply(context, args);
             t_start = t_curr;
         }
         else {
             timer = setTimeout(function(){
                 fn.apply(context, args);
             }, delay);
         }
     };
};

拓展后的節流函數升級版,可以設置第三個參數,即必須觸發執行的時間間隔。

方法調用:
window.onresize = throttleV2(myFunc, 50, 100);

50ms的間隔內連續觸發的調用,后一個調用會把前一個調用的等待處理掉,但每隔100ms至少執行一次。
一開始記錄第一次調用的時間戳,然后每次調用函數都去拿最新的時間跟記錄時間比,超出給定的時間就執行一次,更新記錄時間。

-----------------
例子場景2:實現常見的搜索功能
1.沒有使用函數節流的情況下,為input綁定keyup事件處理函數,在控制台輸出我輸入的內容。
每按下一個鍵盤鍵,就輸出了一次。短短的一些內容,輸出了15次,如果每一次都是一次ajax查詢請求的話就發了14個請求了。在性能上的消耗可想而知。
假如我不斷地輸入,輸入了很多內容,但是我每兩次之間的輸入間隔都小於自己設置的delay值,那么,這個queryData搜索函數就一直得不到調用。實際上,我們更希望的是,當達到某個時間值時,一定要執行一次這個搜索函數。所以,就有了函數節流的改進模式。

函數節流增強版
HTML:
<input id="search" type="text" name="search">
JS:

    <script>
        function queryData(text){
            console.log("搜索:" + text);
        }
        var input = document.getElementById("search");
        input.addEventListener("keyup", function(event){
            throttle(queryData, null, 500, this.value,1000);
            // throttle(queryData, null, 500, this.value);
            // queryData(this.value);
        });
        
        function throttle(fn,context,delay,text,mustApplyTime){
            clearTimeout(fn.timer);
            fn._cur=Date.now();  //記錄當前時間

            if(!fn._start){      //若該函數是第一次調用,則直接設置_start,即開始時間,為_cur,即此刻的時間
                fn._start=fn._cur;
            }
            if(fn._cur-fn._start>mustApplyTime){ 
            //當前時間與上一次函數被執行的時間作差,與mustApplyTime比較,若大於,則必須執行一次函數,若小於,則重新設置計時器
                fn.call(context,text);
                fn._start=fn._cur;
            }else{
                fn.timer=setTimeout(function(){
                    fn.call(context,text);
                },delay);
            }
        }
   </script>

顯然,連續的輸入,到一定時間間隔之后,queryData函數必然會被調用,但是又不是頻繁的調用。達到了節流的目的,又不會影響用戶體驗。

進一步的優化可以在調用throttle函數之前,先對輸入的內容進行判斷,若其值為空、值不變不調用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM