Debounce 和 Throttle 的原理及實現

Debounce 和 Throttle 的原理及實現

當咱們在處理諸如 resize 、 scroll 、 mousemove 和 keydown/keyup/keypress 等事件的時候,一般咱們不但願這些事件太過頻繁地觸發,尤爲是監聽程序中涉及到大量的計算或者有很是耗費資源的操做。若是鼠標連續移動,那麼瀏覽器就應該觸發多個連續的 mousemove 事件,這意味着瀏覽器會在其內部計時器容許的狀況下,根據用戶移動鼠標的速度來觸發 mousemove 事件。瀏覽器

Debounce

DOM 事件裏的 debounce 概念實際上是從機械開關和繼電器的「去彈跳」(debounce) 衍生出來的,基本思路就是把多個信號合併爲一個信號。閉包

在 JavaScript 中, debounce 函數所作的事情就是, 強制一個函數在某個連續時間段內只執行一次,哪怕它原本會被調用屢次 。咱們但願在用戶中止某個操做一段時間以後才執行相應的監聽函數,而不是在用戶操做的過程中,瀏覽器觸發多少次事件,就執行多少次監聽函數。好比,在某個 3s 的時間段內連續地移動了鼠標,瀏覽器可能會觸發幾十(甚至幾百)個 mousemove 事件,不使用 debounce 的話,監聽函數就要執行這麼屢次;若是對監聽函數使用 100ms 的「去彈跳」,那麼瀏覽器只會執行一次這個監聽函數,並且是在第 3.1s 的時候執行的。app

如今,咱們就來實現一個 debounce 函數。函數

/**
* @param fn {Function}   實際要執行的函數
* @param delay {Number}  延遲時間,也就是閾值,單位是毫秒(ms)
* @return {Function}     返回一個「去彈跳」了的函數
*/
function debounce(fn, delay) {
  var timer
  // 返回一個函數,這個函數會在一個時間區間結束後的 delay 毫秒時執行 fn 函數
  return function () {
    // 保存函數調用時的上下文和參數,傳遞給 fn
    var context = this
    var args = arguments
    // 每次這個返回的函數被調用,就清除定時器,以保證不執行 fn
    clearTimeout(timer)
    // 當返回的函數被最後一次調用後(也就是用戶中止了某個連續的操做),
    // 再過 delay 毫秒就執行 fn
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

其實思路很簡單, debounce 返回了一個閉包,這個閉包依然會被連續頻繁地調用,可是在閉包內部,卻限制了原始函數 fn 的執行,強制 fn 只在連續操做中止後只執行一次。
debounce 的使用方式以下:this

$(document).on('mouvemove', debounce(function(e) {
    
}, 200))

咱們在處理防止用戶連續快速點擊按鈕時就能夠採用這種方法解決。spa

Throttle

throttle 的概念理解起來更容易,就是 固定函數執行的速率 ,即所謂的「節流」。正常狀況下, mousemove 的監聽函數可能會每 20ms(假設)執行一次,若是設置 200ms 的「節流」,那麼它就會 每 200ms 執行一次。好比在 1s 的時間段內,正常的監聽函數可能會執行 50(1000/20) 次,「節流」 200ms 後則會執行 5(1000/200) 次。code

/**
*
* @param fn {Function}   實際要執行的函數
* @param delay {Number}  執行間隔,單位是毫秒(ms)
* @return {Function}     返回一個「節流」函數
*/

function throttle(fn, threshhold) {
  // 記錄上次執行的時間
  var last
  var timer

  // 默認間隔爲 250ms
  threshhold || (threshhold = 250)

  // 返回的函數,每過 threshhold 毫秒就執行一次 fn 函數
  return function () {
    // 保存函數調用時的上下文和參數,傳遞給 fn
    var context = this
    var args = arguments
    var now = +new Date()
    // 若是距離上次執行 fn 函數的時間小於 threshhold,那麼就放棄
    // 執行 fn,並從新計時
    if (last && now < last + threshhold) {
      clearTimeout(timer)

      // 保證在當前時間區間結束後,再執行一次 fn
      timer = setTimeout(function () {
        last = now
        fn.apply(context, args)
      }, threshhold)

    // 在時間區間的最開始和到達指定間隔的時候執行一次 fn
    } else {
      last = now
      fn.apply(context, args)
    }
  }
}

原理也不復雜,相比 debounce ,無非是多了一個時間間隔的判斷,其餘的邏輯基本一致。 throttle 的使用方式和debounce同樣。blog

總結

debounce 強制函數在某段時間內只執行一次, throttle 強制函數以固定的速率執行。在處理一些高頻率觸發的 DOM 事件的時候,它們都能極大提升用戶體驗。事件

相關文章
相關標籤/搜索