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

1、函數防抖

  前端開發工做中,咱們常常在一個事件發生後執行某個操做,好比鼠標移動時打印一些東西:前端

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

  這就像電梯,若是一個電梯的設計是每進去一我的就當即關門,那麼若是有10我的排隊進會是怎麼樣呢?多耗電並且很危險。閉包

  解決方法就是每進一我的都從新倒計時N秒再關門,這樣只要每一個人都在前一我的進去N秒以內進門,那麼每進一我的,電梯都會從新計時N秒,因此它只會在最後一我的進門N秒以後再啓動關門程序。app

  這裏有三個關鍵點:「事件是高頻率發生的」、「在前一個發生後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函數呢?this

  由於reTimer函數須要操做來自父級做用域的變量timer,而debounce函數就是爲了建立這樣一個做用域,使得每次執行reTimer函數時timer變量都是存在的。編碼

  若是要求不使用debounce函數,咱們就得把timer變量定義在addEventListener以前:spa

//重置倒計時
 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去訪問,這就是閉包了。code

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

 

2、函數節流

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

  假如咱們正在作一個輸入框,要求每輸入一個字符都調用一個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))

 

 

3、防抖和節流的對比

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

function debounce(fn, delay) {
    let timer = null; 
    return function () {
        var _this = this;         //這裏改了
        clearTimeout(timer);
        timer = setTimeout(function () {
            fn.apply(_this);     //這裏改了
        }, delay);
    };
}
相關文章
相關標籤/搜索