underscore.js 防抖封裝

underscore.js 防抖設置

在實際的工作中,我們經常會遇到限制客戶多次點擊,多次滑動而重復提交代碼的過程,為了能夠有效的防止這種情況,我們今天來討論應該如何處理節流的控制。

前言

setTimeoutclearTimeout

// clearTimeout 雖然取消了定時器,但是timer并沒有取消,只是如果你沒有引用,就會被垃圾回收。
  let timer = setTimeout('console.log(11)')
  clearTimeout(timer)
  console.log(timer) // 20  一個數字

實例重現

我們現在用戶從屏幕的一端滑動另外一端,剛開始count為 1,但是最后為165,就是說,如果這里面是一個復雜的ajax請求,用戶在短短幾秒的過程中,請求了接口165次,假如每次接口的返回的時間是300ms,想想,結果是什么,結果就是用戶會被卡死,作為一個好的開發人員,我們是不是應該阻止這樣的情況發生?

  <style>
    .container {
      background-color: black;
      color: white;
      padding: 100px 0;
      text-align: center;
    }
  </style>
</head>

<body>
  <div class="container"></div>
  <script>
    let count = 1
    const container = document.querySelector('.container');
    function getUserAction() {
      container.innerHTML = count++;
      return 'getUserAction'
    };
    
    container.addEventListener(mousemove, getUserAction)
  </script>
</body>

解決這樣的方式一般有二種情況

  1. 防抖控制debounce
  2. 節流控制throttle

防抖控制

上面說了,主要就是二種情況,我們今天來討論下防抖控制

原理:在一段時間內(n秒),不管用戶怎么點擊,我都不會觸發,只有等到n秒后才會執行,如果中途n秒內,用戶又再次點擊,那我就以用戶新點擊的時間開始重新計算,n秒后才執行。總之:就是在用戶觸發完事件后,n秒內不再觸發,我才執行事件,我就是任性!!!

防抖第一版

我們根據原理可以實現一個函數:

function debounce(func, wait) {
  let timer
  return function() {
    if(timer) clearTimeout(timer)
    timer = setTimeout(func, wait)
  }
}
container.addEventListener(mousemove, debounce(getUserAction, 1000))

這樣處理后,在一秒內,用戶重新觸發事件的話,都不會執行,只有在最后一次觸發1s后才會觸發事件。

防抖第二版

研究一個函數的時候,我們都知道參數this,很重要,所有這里我們也需要處理getUserAction函數內部的this和參數。

如果我們按照第一版不處理的話,getUserAction內部的this就是window了,但是根據事件觸發的this規則,this應該指向事件觸發的dom元素。

加入this:

function debounce(func, wait) {
  let timer, context
  return function() {
    context = this
    if(timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(context)
    }, wait)
  }
}
container.addEventListener(mousemove, debounce(getUserAction, 1000))

處理完this后,是不是要思考每一個事件處理都是有一個事件參數e,我們也需要把這個參數e傳入到getUserAction內部,方便我們處理。

處理參數:

function debounce(func, wait) {
  let timer, context
  return function(...args) {
    context = this
    if(timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(context,args)
    }, wait)
  }
}
container.addEventListener(mousemove, debounce(getUserAction, 1000))

防抖第三版

突然老板來了一個新需求,我不想等n秒后執行,我想立刻執行事件觸發,但是n秒內不再觸發,自己想想好像也是合理的需求。

所有我們給debounce添加一個參數來控制是立即執行還是n秒后再執行

function debounce(func, wait, immediate) {
  let timer, context
  return function(...args) {
    context = this
    if(timer) clearTimeout(timer)
    if(immediate) {
      // 控制是否已經執行過
      call = !timer
      timer = setTimeout(() => {
        timer = null
      },wait)
      if(call) {
        func.apply(this,args)
      } 
    } else {
      timer = setTimeout(() => {
        func.apply(context,args)
      }, wait)
    }
  }
}

這樣處理后,我們就可以根據immediate的值,來用2種方法來執行。

防抖第四版

每一個函數都有一個返回值,getUserAction的返回值如果需要被利用呢?當不是直接執行的時候(immediate=false)討論返回值沒有意義,一直都是undefined(異步),所以只有立即執行的時候,才有返回值。

function debounce(func, wait, immediate) {
  let timer, context, result
  return function(...args) {
    context = this
    if(timer) clearTimeout(timer)
    if(immediate) {
      // 控制是否已經執行過
      call = !timer
      timer = setTimeout(() => {
        timer = null
      },wait)
      if(call) {
        result = func.apply(this,args)
      } 
    } else {
      timer = setTimeout(() => {
        func.apply(context,args)
      }, wait)
    }
    return result
  }
}

做到這里,一個基本的防抖封裝已經完成了,一大部分的情況都可以處理了,但是有沒有想到過一種情況,現實工作中,就是讓用戶點擊一個按鈕,取消等待,繼續觸發函數?

防抖第五版

最后我們再思考一個小需求,我希望能取消 debounce 函數,比如說我 debounce 的時間間隔是 10 秒鐘,immediate 為 true,這樣的話,我只有等 10 秒后才能重新觸發事件,現在我希望有一個按鈕,點擊后,取消防抖,這樣我再去觸發,就可以又立刻執行啦,是不是很開心?

function debounce(func, wait, immediately) {
  let timer
  let debounced =  function (...args) {
    let result
    // 清除鬧鐘后,鬧鐘還是存在的
    if (timer) clearTimeout(timer)
    if (immediately) {
      let called = !timer
      timer = setTimeout(() => {
        timer = null
      }, wait)
      if (called) {
        result = func.apply(this,args)
      }
    } else {
      timer = setTimeout(() => {
        func.apply(this, args)
      }, wait)
    }
    return result
  }
  debounced.cancel = function() {
    clearTimeout(timer)
    timer = null
  }
  return debounced
}

那么如何使用呢?

let action = debounce(getUserAction, 100000, true)
container.addEventListener('mousemove', action)
btn.addEventListener('click', action.cancel)

恭喜你,完成了一個防抖的封裝。


源碼地址

點擊這里

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,065評論 25 708
  • 夜星墮,旭日開,山河斗轉,人世飄零,朝起暮往急匆匆。 春風起,楊柳依,秋霜急雪,四季幻滅,寒來暑往歲相推。 局中人...
    想cx閱讀 30評論 1 4
  • 越圖越毀圖
    水泉印閱讀 247評論 0 1
  • nth-child(1)等于first-childnth-of-type(1)等于first-of-type如圖所...
    我的昵稱去哪了閱讀 524評論 0 0