Throttle and Debounce

先看throttle

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

  if (typeof func !== 'function') {
    throw new TypeError('Expected a function')
  }
  if (isObject(options)) {
    leading = 'leading' in options ? !!options.leading : leading
    trailing = 'trailing' in options ? !!options.trailing : trailing
  }
  return debounce(func, wait, {
    leading,
    trailing,
    'maxWait': wait,
  })
}

可以看到throttle是一个将leadingtrailing都设置为true的debounce函数。这里注意到maxWait也被设置成wait的值,也就是说,节流函数的等待时间 = 防反跳函数的等待时间。

再看debounce:

function debounce(func, wait, options) {
  let lastArgs, // 最后的参数
    lastThis, // 最后的this
    maxWait, // 最大等待时间
    result, // 结果
    timerId, // 计时器ID
    lastCallTime // 上次call的时间

  let lastInvokeTime = 0 // 上次执行时间
  let leading = false // debounce是false,throttle是true。
  let maxing = false // 
  let trailing = true // debouce是false,throttle是true。

  // Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
  // RAF的hacking。感觉很奇怪...
  const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function')

  if (typeof func !== 'function') {
    throw new TypeError('Expected a function')
  }
  // wait 强制转换为数字
  wait = +wait || 0
  if (isObject(options)) {
    leading = !!options.leading
    maxing = 'maxWait' in options
    maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
    trailing = 'trailing' in options ? !!options.trailing : trailing
  }
  // 执行函数的辅助函数
  function invokeFunc(time) {
    // 记录上次的参数,上次的this scope。
    const args = lastArgs
    const thisArg = lastThis
    // 让上次的参数和scope复位成undefined。
    lastArgs = lastThis = undefined
    lastInvokeTime = time
    // 用apply执行func。这里是最终执行传入函数的地方。
    result = func.apply(thisArg, args)
    return result
  }
  // 计时器的辅助函数,传入等待的函数,等待的时间。
  // 也就是setTimeout的RAF兼容版本
  function startTimer(pendingFunc, wait) {
    if (useRAF) {
      root.cancelAnimationFrame(timerId);
      return root.requestAnimationFrame(pendingFunc)
    }
    return setTimeout(pendingFunc, wait)
  }
  // 同样,这里是clearTimeout的兼容版本。
  function cancelTimer(id) {
    if (useRAF) {
      return root.cancelAnimationFrame(id)
    }
    clearTimeout(id)
  }
  // 执行“前置”函数的方法
  function leadingEdge(time) {
    // Reset any `maxWait` timer.
    // 重置maxWait的时间为time参数
    lastInvokeTime = time
    // 让计时器计时到wait ms后
    // Start the timer for the trailing edge.
    timerId = startTimer(timerExpired, wait)
    // Invoke the leading edge.
    // 执行“前置”函数
    return leading ? invokeFunc(time) : result
  }
  // 计算等待的时间 
  function remainingWait(time) {
    const timeSinceLastCall = time - lastCallTime
    const timeSinceLastInvoke = time - lastInvokeTime
    const timeWaiting = wait - timeSinceLastCall
    // 这里是防止一个函数太久没有执行,maxWait保证函数超时。
    return maxing
      ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
      : timeWaiting
  }

  function shouldInvoke(time) {
    const timeSinceLastCall = time - lastCallTime
    const timeSinceLastInvoke = time - lastInvokeTime

    // 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))
  }

  function timerExpired() {
    const time = Date.now()
    if (shouldInvoke(time)) {
      return trailingEdge(time)
    }
    // Restart the timer.
    timerId = startTimer(timerExpired, remainingWait(time))
  }

  function trailingEdge(time) {
    timerId = undefined

    // Only invoke if we have `lastArgs` which means `func` has been
    // debounced at least once.
    if (trailing && lastArgs) {
      return invokeFunc(time)
    }
    lastArgs = lastThis = undefined
    return result
  }

  function cancel() {
    if (timerId !== undefined) {
      cancelTimer(timerId)
    }
    lastInvokeTime = 0
    lastArgs = lastCallTime = lastThis = timerId = undefined
  }

  function flush() {
    return timerId === undefined ? result : trailingEdge(Date.now())
  }

  function pending() {
    return timerId !== undefined
  }

  function debounced(...args) {
    const time = Date.now()
    const isInvoking = shouldInvoke(time)

    lastArgs = args
    lastThis = this
    lastCallTime = time

    if (isInvoking) {
      if (timerId === undefined) {
        return leadingEdge(lastCallTime)
      }
      if (maxing) {
        // Handle invocations in a tight loop.
        timerId = startTimer(timerExpired, wait)
        return invokeFunc(lastCallTime)
      }
    }
    if (timerId === undefined) {
      timerId = startTimer(timerExpired, wait)
    }
    return result
  }
  debounced.cancel = cancel
  debounced.flush = flush
  debounced.pending = pending
  return debounced
}