function/debounce.jsjavascript
/**
 * 创建防抖函数,支持前缘、尾调用以及 cancel/flush 控制。
 * @function debounce
 * @memberof module:function
 * @param {Function} fn 目标函数
 * @param {number} [wait=0] 等待毫秒数
 * @param {Object} [options] 执行选项
 * @param {boolean} [options.leading=false] 是否在等待开始时立即执行
 * @param {boolean} [options.trailing=true] 是否在等待结束后执行
 * @returns {Function} 带 cancel 和 flush 方法的防抖函数
 * @throws {TypeError|RangeError} 参数类型或 wait 不合法时抛出
 */
function debounce(fn, wait = 0, options = {}) {
  if (typeof fn !== 'function') throw new TypeError('fn must be a function')
  if (!Number.isFinite(wait) || wait < 0) throw new RangeError('wait must be a non-negative finite number')
  if (options === null || typeof options !== 'object' || Array.isArray(options)) throw new TypeError('options must be an object')
  const leading = options.leading ?? false
  const trailing = options.trailing ?? true
  if (typeof leading !== 'boolean' || typeof trailing !== 'boolean') throw new TypeError('leading and trailing must be booleans')
  let timerId = null
  let lastArgs
  let lastThis
  let result

  const invoke = () => {
    const args = lastArgs
    const context = lastThis
    lastArgs = undefined
    lastThis = undefined
    result = fn.apply(context, args)
    return result
  }
  const debounced = function (...args) {
    const callNow = leading && timerId === null
    lastArgs = args
    lastThis = this
    if (timerId !== null) clearTimeout(timerId)
    timerId = setTimeout(() => {
      timerId = null
      if (trailing && lastArgs) invoke()
      else {
        lastArgs = undefined
        lastThis = undefined
      }
    }, wait)
    if (callNow) invoke()
    return result
  }
  debounced.cancel = () => {
    if (timerId !== null) clearTimeout(timerId)
    timerId = null
    lastArgs = undefined
    lastThis = undefined
  }
  debounced.flush = () => {
    if (timerId === null) return result
    clearTimeout(timerId)
    timerId = null
    if (trailing && lastArgs) return invoke()
    lastArgs = undefined
    lastThis = undefined
    return result
  }
  return debounced
}

export default debounce