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

114

_.throttle(func, [wait=0], [options={}])
创建一个节流函数,每调用一次func函数的时候至少要等待`wait`毫秒(或者每一次调用等待浏览器重绘页面帧)。节流函数有一个cancel方法来取消延迟的func调用,还有一个flush方法来立即调用func函数。也提供了option参数来表明func函数是否应该在等待wait时间开始之前调用还是wait时间过后调用。func函数调用会带着提供给节流函数的最后一个配置参数。之后对于节流函数的调用返回最后一次func调用的结果。
注意:如果leading和trailing配置项是true,func只有在节流函数等待wait时间度过的时候被调用超过一次时才会在wait时延过后再次调用。
如果wait等待时间是0,并且leading是false,func的调用会推迟至下一次事件循环,与setTimeout设置延迟时间为0一样
如果wait参数在使用requestAnimationFrame的环境中被省略,func会延迟至下一次页面重绘的时候被调用,通常延迟16ms
//使用场景和例子
//当滚动页面的时候避免过度更新滚动位置
/*
jQuery(window).on('scroll', throttle(updatePosition, 100))
*/

//当点击事件被触发的时候调用renewToken,但是每五分钟才能调用一次
/*
const throttled = throttle(renewToken, 300000, { 'trailing': false })
jQuery(element).on('click', throttled)
*/

//当页面路由变化的时候,取消等待时间过后结尾的节流函数调用
//jQuery(window).on('popstate', throttled.cancel)

参数

func (Function): 需要节流的函数
[wait=0] (number): 需要节流调用等待的时间
[options={}] (Object): 配置对象
[options.leading=true] (boolean): 在func被调用之前的最大延迟时间
[options.trailing=true] (boolean): 是否在wait时延过后调用

返回值

(Function): 返回新的节流函数

例子

// Avoid excessively updating the position while scrolling.
jQuery(window).on('scroll', _.throttle(updatePosition, 100));
 
// Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
jQuery(element).on('click', throttled);
 
// Cancel the trailing throttled invocation.
jQuery(window).on('popstate', throttled.cancel);

节流与去抖的区别

throttle节流函数与debounce去抖函数之间的区别
节流函数默认leading和trailing都是true,所以在等待时间内,如果触发超过一次,那么wait时延过后就会主动触发
去抖函数默认leading为false,trailing为true,如果一直不停的触发,那么会不停地重置定时器,所以直到停止不停触发事件结束后等待最后一个wait时间过后才会触发事件处理程序

源代码

import debounce from './debounce.js'
import isObject from './isObject.js'

/**
 * Creates a throttled function that only invokes `func` at most once per
 * every `wait` milliseconds (or once per browser frame). The throttled 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
 * throttled function. Subsequent calls to the throttled 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 throttled 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 `throttle` and `debounce`.
 *
 * @since 0.1.0
 * @category Function
 * @param {Function} func The function to throttle.
 * @param {number} [wait=0]
 *  The number of milliseconds to throttle invocations to; if omitted,
 *  `requestAnimationFrame` is used (if available).
 * @param {Object} [options={}] The options object.
 * @param {boolean} [options.leading=true]
 *  Specify invoking on the leading edge of the timeout.
 * @param {boolean} [options.trailing=true]
 *  Specify invoking on the trailing edge of the timeout.
 * @returns {Function} Returns the new throttled function.
 * @example
 *
 * // Avoid excessively updating the position while scrolling.
 * jQuery(window).on('scroll', throttle(updatePosition, 100))
 *
 * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
 * const throttled = throttle(renewToken, 300000, { 'trailing': false })
 * jQuery(element).on('click', throttled)
 *
 * // Cancel the trailing throttled invocation.
 * jQuery(window).on('popstate', throttled.cancel)
 */

//创建一个节流函数,每调用一次func函数的时候至少要等待`wait`毫秒(或者每一次调用等待浏览器重绘页面帧)。节流函数有一个cancel方法来取消延迟的func调用,还有一个flush方法来立即调用func函数。也提供了option参数来表明func函数是否应该在等待wait时间开始之前调用还是wait时间过后调用。func函数调用会带着提供给节流函数的最后一个配置参数。之后对于节流函数的调用返回最后一次func调用的结果。
//注意:如果leading和trailing配置项是true,func只有在节流函数等待wait时间度过的时候被调用超过一次时才会在wait时延过后再次调用。
//如果wait等待时间是0,并且leading是false,func的调用会推迟至下一次事件循环,与setTimeout设置延迟时间为0一样
//如果wait参数在使用requestAnimationFrame的环境中被省略,func会延迟至下一次页面重绘的时候被调用,通常延迟16ms

//使用场景和例子
//当滚动页面的时候避免过度更新滚动位置
/* 
jQuery(window).on('scroll', throttle(updatePosition, 100))
*/

//当点击事件被触发的时候调用renewToken,但是每五分钟才能调用一次
/*
const throttled = throttle(renewToken, 300000, { 'trailing': false })
jQuery(element).on('click', throttled)
*/

//当页面路由变化的时候,取消等待时间过后结尾的节流函数调用
//jQuery(window).on('popstate', throttled.cancel)

function throttle(func, wait, options) {
  let leading = true
  let trailing = true

  if (typeof func != 'function') {//如果func不是function,抛出错误
    throw new TypeError('Expected a function')
  }
  if (isObject(options)) {//提取options中的leading和trailing参数,默认都是true
    leading = 'leading' in options ? !!options.leading : leading
    trailing = 'trailing' in options ? !!options.trailing : trailing
  }
  return debounce(func, wait, {//调用去抖函数处理
    'leading': leading,
    'maxWait': wait,
    'trailing': trailing
  })
}

export default throttle

debounce

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

 

posted @ 2018-12-10 12:12  hahazexia  阅读(1028)  评论(0)    收藏  举报