LeoWangJ / blog

紀錄學習文章
1 stars 0 forks source link

防抖(debounce) 實現 #11

Open LeoWangJ opened 5 years ago

LeoWangJ commented 5 years ago

防抖(debounce)

在功能頻繁觸發的情況下,當只有在功能觸發的時間間隔超過我們所指定的時間,才會觸發此功能

簡單來說就是某個函數在某個指定的時間內不管觸發多少次, 都只會執行最後一次的觸發。

舉例來說: 我們設了一個五秒的指定時間, 則在這五秒內每觸發了一次函式,就會重新計算這五秒, 直到最後一次五秒內都沒有重新觸發函式時就會執行任務。

常用場景:

  1. window的resize,scroll 事件
  2. input的keyup等其他事件
  3. 滑鼠拖曳的mousemove事件

有時候我們只是想知道最後的結果,但上述這些方法在發生變動時會頻繁觸發,因此我們需要使用防抖來節省資源。

例如註冊帳號時,我們需要去檢驗此帳號是否已經被註冊過了,但需求方希望可以在使用者打字時就可以監測,而不是輸入框失去焦點時才判斷。

這時我們可能會這樣寫


$(".register").on("input", function(){
   // 請求後端來判斷是否使用者已經有被註冊,但這個函式不是我們主要探討的部分
  handleRegisterName(this.value)
})

當今天我想要將帳號名稱取為leowang時,會發現我們每次輸入文字都會觸發handleRegisterName這個方法,這樣就多請求許多次不必要的資源。

這時我們不就可以使用防抖來解決這個問題了嗎? 那防抖怎麼寫呢? 你想到了上面的那行 "當只有在功能觸發的時間間隔超過我們所指定的時間,才會觸發此功能"。 這時需要setTimeout來判斷時間是否有超過間隔以及定義兩個參數,分別是要觸發的函式以及指定的時間

 // 防抖函式
 function debounce(fn , wait){
     var timeout
     return function(){
        if(timeout) clearTimeout(timeout)
        timeout = setTimeout(fn, wait)
    }
 }

$("#register").on("input", debounce(function(){
  console.log(this.value)
  handleRegisterName(this.value)
}, 300))

修正this問題與傳遞參數

完成後發現了this.value 怎麼是undefined? 檢查一下發現this被指向了window, 這是因為是setTimeout呼叫了這個回呼函式,除了這個問題之外,我們還想要傳遞一些參數進去函式中,例如event。所以我們必須調整一下debounce函式。

透過apply方式綁定上下文情境以及使用arguments取得函數參數。

 function debounce(fn, wait) {
   var timeout, args
   return function(){
      args = arguments
      if(timeout) clearTimeout(timeout)
      timeout = setTimeout(() => {
          fn.apply(this, args)  
      }, wait)
  }
 }

立即執行功能

這時需求方又說希望一輸入文字時,就先執行一次。所以我們就當時間到時將timeout 設成null,使得可以直接執行fn方法

 function debounce(fn, wait, imd) {
   var timeout,args
   return function(){
     args = arguments
     if(timeout) clearTimeout(timeout)
     if(imd) {
       var callNow = !timeout;
       timeout = setTimeout(function(){
         timeout = null;
       }, wait)
       if (callNow) fn.apply(this, args)

     } else {
        timeout = setTimeout(() => {
            fn.apply(this, args)  
        }, wait)
     }
  } 
 }

返回值

但上面還有一個問題, 就是我們的fn假如有回傳值時, 上面那一個版本是無法接收的, 所以我們必須修正這個問題。

但要注意的是, 回傳值版本只提供給立即執行, 因為在非立即執行中fn是在setTimeout內, 導致我們的return的值永遠都是undefined 。

  var debounce = function(fn, wait, immediate){
      var timeout,result,args
      return fuction() {
          args = arguments
          if(timeout) clearTimeout(timeout)
          if(immediate) {
              var callNow = !timeout
              timeout = setTimeout(function(){
                  timeout = null;
              }, wait) 
              if(callNow) result = fn.apply(this, args)
          } else {
              timeout = setTimeout(() => {
                   fn.apply(this, args)
             }, wait)
          }
          return result
      }
  }
參考

从搜索系统来聊聊防抖和节流 JavaScript专题之跟着underscore学防抖