JS防抖和節流:原來如此簡單


一、函數防抖

  前端開發工作中,我們經常在一個事件發生后執行某個操作,比如鼠標移動時打印一些東西:

1 window.addEventListener("mousemove", ()=>console.log(123));
2  //測試發現鼠標移動了1毫米,回調函數執行了將近10次,這種做法是非常耗費資源的。

  這就像電梯,如果一個電梯的設計是每進去一個人就立即關門,那么如果有10個人排隊進會是怎么樣呢?多耗電而且很危險。

  解決方法就是每進一個人都重新倒計時N秒再關門,這樣只要每個人都在前一個人進去N秒之內進門,那么每進一個人,電梯都會重新計時N秒,所以它只會在最后一個人進門N秒之后再啟動關門程序。

  這里有三個關鍵點:「事件是高頻率發生的」、「在前一個發生后N秒內發生下一個」、「重新倒計時」

  函數防抖(debounce)就是以上解決方案的JavaScript實現,上面代碼的改寫思路是:每次事件發生后都只做兩件事——「清除舊的計時器」、「設置新的計時器」

//重置計時器的函數
 function debounce(func, ms){
     let timer = null;
     function reTimer(){
         //重新計時
         clearTimeout(timer)
         timer = setTimeout(func, ms)
     }
     return reTimer;
 }
 ​
 //要執行的動作
 function handle(){
   console.log("--- do something ---")
 }
 ​
 //綁定事件:每次鼠標移動時,就會執行debounce返回的reTimer函數
 window.addEventListener("mousemove", debounce(handle, 1000))

 

進一步分析

  前面說了,每次事件發生后都只做兩件事——「清除舊的計時器」、「設置新的計時器」,那么為什么要在addEventListener里執行debounce函數呢?

  因為reTimer函數需要操作來自父級作用域的變量timer,而debounce函數就是為了創建這樣一個作用域,使得每次執行reTimer函數時timer變量都是存在的。

  如果要求不使用debounce函數,我們就得把timer變量定義在addEventListener之前:

//重置倒計時
 function reTimer(){
   if(timer){
     clearTimeout(timer)
   }
   timer = setTimeout(handle, 1000)
 }
 ​
 //事件處理
 function handle(){
   console.log("--- do something ---")
 }
 ​
 //事件綁定
 let timer = null; //或者 window.timer = null
 window.addEventListener("mousemove", reTimer)

  不過,相比使用debounce函數,這樣做就不那么優雅了。

  addEventListener的目的是操作timer變量,而timer在debounce的作用域內,addEventListener訪問不到,所以用debounce返回的reTimer去訪問,這就是閉包了。

  防抖是讓重復事件的處理函數只在最后一次發生時執行,而閉包只是一個更好的實現方案。

 

二、函數節流

  理解了函數防抖,函數節流也就好辦了,我們只需要理解場景和方案。

  假如我們正在做一個輸入框,要求每輸入一個字符都調用一個API來查詢數據,從而實現聯想、自動補全等功能,然而我們的輸入速度是很快的,可能還沒等第一個字符的查詢結果出來,第二個字符就已經敲進去了,所以我們需要讓查詢頻率小一點,具體做法就是在輸入的過程中,每隔N秒才查詢一次。

  這里的關鍵點是:「事件是高頻率發生的」、「在前一個發生后N秒內發生下一個」、「一個計時結束后再重新計時」

定時器實現節流

  節流函數(throttle)要做的就是:「確保一個計時器停止時再重新計時」 

/*
  * 節流函數生成器
  *    傳遞事件處理函數和延遲時間
  *    返回節流函數
  */
 function throttleGen(fn, delay) {
   let timer = null;
   function throller() {
     if (timer === null) {
       timer = setTimeout(function () {
         fn();
         timer = null;
       }, delay)
     }
   }
 ​
   return throller;
 }
 ​
 //事件處理函數
 function handle() {
   console.log('-- do something --');
 }
 ​
 ​
 //綁定事件
 window.addEventListener("mousemove", throttleGen(handle, 1000))

 

 

三、防抖和節流的對比

  用輸入時執行動作的例子可以對比防抖和節流,防抖就是等最后一個字符輸入完N秒之后再查詢,而節流是在輸入過程中每隔N秒查詢一次。
  以上代碼為了保持簡單,刻意忽略了綁定上下文等操作,在實際編碼過程中,只要稍加改動即可使用穩定可靠的防抖和節流函數,比如這樣:

function debounce(fn, delay) {
    let timer = null; 
    return function () {
        var _this = this;         //這里改了
        clearTimeout(timer);
        timer = setTimeout(function () {
            fn.apply(_this);     //這里改了
        }, delay);
    };
}

 


免責聲明!

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



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