_.debounce(func, [wait=0], [options={}])


101

_.debounce(func, [wait=0], [options={}])
_.debounce創建一個去抖函數來推遲調用func,自從上一次去抖函數被調用之后等待wait毫秒時間過后再調用,或者等待直到下一次瀏覽器幀被重新繪制。創建去抖函數的同時也會創建一個cancel方法去取消延遲func調用,還有一個flush方法來立即調用。也提供了option參數來表明func函數是否應該在等待wait時間開始之前調用還是wait時間過后調用。func函數調用會帶着提供給去抖函數的最后一個配置參數。之后對於去抖函數的調用返回最后一次func調用的結果。
注意:如果leading和trailing配置項是true,func只有在去抖函數等待wait時間度過的時候被調用超過一次時才會在wait時延過后再次調用。
如果wait等待時間是0,並且leading是false,func的調用會推遲至下一次事件循環,與setTimeout設置延遲時間為0一樣
如果wait參數在使用requestAnimationFrame的環境中被省略,func會延遲至下一次頁面重繪的時候被調用,通常延遲16ms
 
使用場景
當window大小不斷變化的時候避免昂貴的計算,例如
jQuery(window).on('resize', debounce(calculateLayout, 150))
 
當點擊的時候調用`sendMail`,將隨后的調用去抖
jQuery(element).on('click', debounce(sendMail, 300, {
    'leading': true,
    'trailing': false
}))
確保`batchLog`在去抖化調用后1秒鍾被調用1次
const debounced = debounce(batchLog, 250, { 'maxWait': 1000 })
const source = new EventSource('/stream')
jQuery(source).on('message', debounced)
取消wait時延過后的去抖調用
jQuery(window).on('popstate', debounced.cancel)

檢查是否調用處於等待狀態

const status = debounced.pending() ? "Pending..." : "Ready"

參數

func (Function): 需要被去抖的函數
[wait=0] (number): 推遲執行的毫秒數,如果省略這個參數,requestAnimationFrame會被使用
[options={}] (Object): 配置對象
[options.leading=false] (boolean): 是否在wait時延之前調用
[options.maxWait] (number): 在func被調用之前的最大延遲時間
[options.trailing=true] (boolean): 是否在wait時延過后調用

返回值

(Function): 返回新的被去抖的函數

例子

// Avoid costly calculations while the window size is in flux.
jQuery(window).on('resize', _.debounce(calculateLayout, 150));
 
// Invoke `sendMail` when clicked, debouncing subsequent calls.
jQuery(element).on('click', _.debounce(sendMail, 300, {
  'leading': true,
  'trailing': false
}));
 
// Ensure `batchLog` is invoked once after 1 second of debounced calls.
var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
var source = new EventSource('/stream');
jQuery(source).on('message', debounced);
 
// Cancel the trailing debounced invocation.
jQuery(window).on('popstate', debounced.cancel);

去抖流程

首次進入函數時因為 lastCallTime === undefined 並且 timerId === undefined,所以會執行 leadingEdge,如果此時 leading 為 true 的話,就會執行 func。同時,這里會設置一個定時器,在等待 wait(s) 后會執行 timerExpired,timerExpired 的主要作用就是觸發 trailing。

如果在還未到 wait 的時候就再次調用了函數的話,會更新 lastCallTime,並且因為此時 isInvoking 不滿足條件,所以這次什么也不會執行。

時間到達 wait 時,就會執行我們一開始設定的定時器timerExpired,此時因為time-lastCallTime < wait,所以不會執行 trailingEdge。

這時又會新增一個定時器,下一次執行的時間是 remainingWait,這里會根據是否有 maxwait 來作區分:

如果沒有 maxwait,定時器的時間是 wait - timeSinceLastCall,保證下一次 trailing 的執行。

如果有 maxing,會比較出下一次 maxing 和下一次 trailing 的最小值,作為下一次函數要執行的時間。

最后,如果不再有函數調用,就會在定時器結束時執行 trailingEdge。

下面是流程圖片描述:

節流與去抖的區別

throttle節流函數與debounce去抖函數之間的區別
節流函數默認leading和trailing都是true,所以在等待時間內,如果觸發超過一次,那么wait時延過后就會主動觸發
去抖函數默認leading為false,trailing為true,如果一直不停的觸發,那么會不停地重置定時器,所以直到停止不停觸發事件結束后等待最后一個wait時間過后才會觸發事件處理程序

源代碼:

import isObject from './isObject.js'
import root from './.internal/root.js'

/**
 * Creates a debounced function that delays invoking `func` until after `wait`
 * milliseconds have elapsed since the last time the debounced function was
 * invoked, or until the next browser frame is drawn. The debounced function
 * comes with a `cancel` method to cancel delayed `func` invocations and a
 * `flush` method to immediately invoke them. Provide `options` to indicate
 * whether `func` should be invoked on the leading and/or trailing edge of the
 * `wait` timeout. The `func` is invoked with the last arguments provided to the
 * debounced function. Subsequent calls to the debounced function return the
 * result of the last `func` invocation.
 *
 * **Note:** If `leading` and `trailing` options are `true`, `func` is
 * invoked on the trailing edge of the timeout only if the debounced function
 * is invoked more than once during the `wait` timeout.
 *
 * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
 * until the next tick, similar to `setTimeout` with a timeout of `0`.
 *
 * If `wait` is omitted in an environment with `requestAnimationFrame`, `func`
 * invocation will be deferred until the next frame is drawn (typically about
 * 16ms).
 *
 * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
 * for details over the differences between `debounce` and `throttle`.
 *
 * @since 0.1.0
 * @category Function
 * @param {Function} func The function to debounce.
 * @param {number} [wait=0]
 *  The number of milliseconds to delay; if omitted, `requestAnimationFrame` is
 *  used (if available).
 * @param {Object} [options={}] The options object.
 * @param {boolean} [options.leading=false]
 *  Specify invoking on the leading edge of the timeout.
 * @param {number} [options.maxWait]
 *  The maximum time `func` is allowed to be delayed before it's invoked.
 * @param {boolean} [options.trailing=true]
 *  Specify invoking on the trailing edge of the timeout.
 * @returns {Function} Returns the new debounced function.
 * @example
 *
 * // Avoid costly calculations while the window size is in flux.
 * jQuery(window).on('resize', debounce(calculateLayout, 150))
 *
 * // Invoke `sendMail` when clicked, debouncing subsequent calls.
 * jQuery(element).on('click', debounce(sendMail, 300, {
 *   'leading': true,
 *   'trailing': false
 * }))
 *
 * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
 * const debounced = debounce(batchLog, 250, { 'maxWait': 1000 })
 * const source = new EventSource('/stream')
 * jQuery(source).on('message', debounced)
 *
 * // Cancel the trailing debounced invocation.
 * jQuery(window).on('popstate', debounced.cancel)
 *
 * // Check for pending invocations.
 * const status = debounced.pending() ? "Pending..." : "Ready"
 */

//創建一個去抖函數來推遲調用func,自從上一次去抖函數被調用之后等待wait毫秒時間過后再調用,或者等待直到下一次瀏覽器幀被重新繪制。創建去抖函數的同時也會創建一個cancel方法去取消延遲func調用,還有一個flush方法來立即調用。也提供了option參數來表明func函數是否應該在等待wait時間開始之前調用還是wait時間過后調用。func函數調用會帶着提供給去抖函數的最后一個配置參數。之后對於去抖函數的調用返回最后一次func調用的結果。
//注意:如果leading和trailing配置項是true,func只有在去抖函數等待wait時間度過的時候被調用超過一次時才會在wait時延過后再次調用。
//如果wait等待時間是0,並且leading是false,func的調用會推遲至下一次事件循環,與setTimeout設置延遲時間為0一樣
//如果wait參數在使用requestAnimationFrame的環境中被省略,func會延遲至下一次頁面重繪的時候被調用,通常延遲16ms

//使用場景
//當window大小不斷變化的時候避免昂貴的計算,例如jQuery(window).on('resize', debounce(calculateLayout, 150))

//當點擊的時候調用`sendMail`,將隨后的調用去抖
// jQuery(element).on('click', debounce(sendMail, 300, {
//    'leading': true,
//    'trailing': false
// }))

//確保`batchLog`在去抖化調用后1秒鍾被調用1次
// const debounced = debounce(batchLog, 250, { 'maxWait': 1000 })
// const source = new EventSource('/stream')
// jQuery(source).on('message', debounced)

//取消wait時延過后的去抖調用
//jQuery(window).on('popstate', debounced.cancel)

//檢查是否調用處於等待狀態
//const status = debounced.pending() ? "Pending..." : "Ready"

//func是需要去抖的方法
//wait推遲執行的毫秒數,如果省略這個參數,requestAnimationFrame會被使用
//requestAnimationFrame瀏覽器在下一次重繪之前調用指定的函數
//options中的配置項
//options.leading在wait時延之前調用
//options.trailing在wait時延過后調用
//options.maxWait在func被調用之前的最大延遲時間
function debounce(func, wait, options) {
  let lastArgs,
    lastThis,
    maxWait,//func調用之前的最大延遲時間
    result,
    timerId,//定時器
    lastCallTime

  let lastInvokeTime = 0//上一次調用func的時間
  let leading = false//是否在wait時延之前調用
  let maxing = false//是否設置了func調用之前的最大延遲時間
  let trailing = true//是否在wait時延過后調用

  // Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
  //忽略requestAnimationFrame通過明確地設置wait=0
  const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function')

  if (typeof func != 'function') {//如果func不是function類型,拋出錯誤
    throw new TypeError('Expected a function')
  }
  wait = +wait || 0//將wait轉換成數字
  if (isObject(options)) {//如果options是對象
    leading = !!options.leading//是否在wait時延之前調用
    maxing = 'maxWait' in options//是否設置了func調用之前的最大延遲時間
    maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait//func調用之前的最大延遲時間
    trailing = 'trailing' in options ? !!options.trailing : trailing//是否在wait時延過后調用
  }

  function invokeFunc(time) {//執行func
    const args = lastArgs
    const thisArg = lastThis

    lastArgs = lastThis = undefined//debounced接收參數和this置空
    lastInvokeTime = time//更新上一次執行func時間
    result = func.apply(thisArg, args)//調用func獲取結果返回
    return result
  }

  function startTimer(pendingFunc, wait) {//啟動定時器
    if (useRAF) {
      return root.requestAnimationFrame(pendingFunc)
    }
    return setTimeout(pendingFunc, wait)
  }

  function cancelTimer(id) {//取消定時器
    if (useRAF) {
      return root.cancelAnimationFrame(id)
    }
    clearTimeout(id)
  }

  function leadingEdge(time) {//leading時調用
    // Reset any `maxWait` timer.
    lastInvokeTime = time//重置上一次func執行時間
    // Start the timer for the trailing edge.
    timerId = startTimer(timerExpired, wait)//為wait時延過后的trailing調用開啟定時器
    // Invoke the leading edge.
    return leading ? invokeFunc(time) : result//如果leading為true,wait時延之前調用,直接調用,否則返回result,undefined
  }

  function remainingWait(time) {//還剩下多少時間可以執行下一次
    const timeSinceLastCall = time - lastCallTime//距離上次去抖時間
    const timeSinceLastInvoke = time - lastInvokeTime//距離上次func執行時間
    const timeWaiting = wait - timeSinceLastCall//距離上次去抖時間還需要等多久,下一次trailing時間

    return maxing
      ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
      : timeWaiting
      //如果有傳遞最大等待時間,就在下一次trailing時間和maxWait - timeSinceLastInvoke里找最小的
      //如果沒有傳遞最大等待時間,返回下一次trailing時間
  }

  function shouldInvoke(time) {//判斷在time時間點是否func應該被調用
    const timeSinceLastCall = time - lastCallTime//距離上次去抖時間
    const timeSinceLastInvoke = time - lastInvokeTime//距離上次func執行時間

    // Either this is the first call, activity has stopped and we're at the
    // trailing edge, the system time has gone backwards and we're treating
    // it as the trailing edge, or we've hit the `maxWait` limit.
    return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
      (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
      //lastCallTime === undefined首次調用
      //timeSinceLastCall >= wait距離上次被去抖已經超過 wait
      //timeSinceLastCall < 0//系統時間倒退
      //maxing && timeSinceLastInvoke >= maxWait//距離上次func執行時間已經超過最大等待時間
  }

  function timerExpired() {//等待wait時間后觸發trailing
    const time = Date.now()//當前時間
    if (shouldInvoke(time)) {//如果當前時間應該調用,就調用trailingEdge判斷是否已經去抖過一次
      return trailingEdge(time)
    }
    // Restart the timer.
    timerId = startTimer(timerExpired, remainingWait(time))//如果此刻不能執行func,就重新啟動定時器,時間為剩余等待時間
  }

  function trailingEdge(time) {//trailing時調用func
    timerId = undefined//定時器置空

    // Only invoke if we have `lastArgs` which means `func` has been
    // debounced at least once.
    if (trailing && lastArgs) {//只有在擁有lastArgs參數的時候才執行func,lastArgs說明func被去抖至少一次
      return invokeFunc(time)
    }
    lastArgs = lastThis = undefined//lastArgs和lastThis置空
    return result//返回結果undefined
  }

  function cancel() {//取消定時器,並將上一次調用時間清0,上一次debounced接收參數,this,上一次去抖時間都置空
    if (timerId !== undefined) {
      cancelTimer(timerId)
    }
    lastInvokeTime = 0
    lastArgs = lastCallTime = lastThis = timerId = undefined
  }

  function flush() {//如果沒有定時器,返回當前結果,如果有定時器,調用trailingEdge
    return timerId === undefined ? result : trailingEdge(Date.now())
  }

  function pending() {//查看當前是否處於等待狀態
    return timerId !== undefined
  }

  function debounced(...args) {
    const time = Date.now()//當前時間毫秒數
    const isInvoking = shouldInvoke(time)//判斷當前時間是否func應該被調用

    lastArgs = args//參數數組
    lastThis = this//本次this
    lastCallTime = time//去抖時間

    if (isInvoking) {//如果此刻需要被調用,1說明是首次調用且是leading調用 2距離上次去抖wait時間了 3距離上次執行時間過去了最大等待時間
      if (timerId === undefined) {//如果沒有定時器說明1首次調用2剛執行過取消操作或者trailing調用
        return leadingEdge(lastCallTime)//調用leadingEdge判斷是否leading時需要調用
      }
      if (maxing) {//如果不是第一次調用且設置了最大延遲時間,說明已經超過了最大延遲時間,直接調用返回結果
        // Handle invocations in a tight loop.
        timerId = startTimer(timerExpired, wait)//開啟一個定時器,等待時間wait,判斷是否wait時間后需要出發trailing調用
        return invokeFunc(lastCallTime)//調用func返回結果
      }
    }
    if (timerId === undefined) {//如果此刻不需要被調用,且定時器沒有開啟,就開啟一個定時器,等待時間wait
      timerId = startTimer(timerExpired, wait)
    }
    return result
  }
  debounced.cancel = cancel
  debounced.flush = flush
  debounced.pending = pending
  return debounced
}

export default debounce

 


免責聲明!

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



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