实现防抖和节流

手写 debounce 和 throttle,支持立即执行与取消

问题

实现防抖 (debounce) 和节流 (throttle) 函数,要求:

  • 支持立即执行选项
  • 支持取消功能
  • 正确处理 this 和参数

解答

防抖 (Debounce)

事件触发后等待一段时间才执行,如果期间再次触发则重新计时。

function debounce(fn, wait, immediate = false) {
  let timer = null;

  const debounced = function (...args) {
    // 清除之前的定时器
    if (timer) clearTimeout(timer);

    if (immediate) {
      // 立即执行模式:第一次触发立即执行
      const callNow = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, wait);
      if (callNow) fn.apply(this, args);
    } else {
      // 延迟执行模式:等待结束后执行
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, wait);
    }
  };

  // 取消功能
  debounced.cancel = function () {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
  };

  return debounced;
}

节流 (Throttle)

规定时间内只执行一次,无论触发多少次。

function throttle(fn, wait, options = {}) {
  let timer = null;
  let previous = 0;
  // leading: 是否在开始时立即执行
  // trailing: 是否在结束后执行一次
  const { leading = true, trailing = true } = options;

  const throttled = function (...args) {
    const now = Date.now();

    // 首次调用且不需要立即执行
    if (!previous && !leading) {
      previous = now;
    }

    const remaining = wait - (now - previous);

    if (remaining <= 0) {
      // 时间到了,执行函数
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      previous = now;
      fn.apply(this, args);
    } else if (!timer && trailing) {
      // 设置定时器,保证最后一次触发能执行
      timer = setTimeout(() => {
        previous = leading ? Date.now() : 0;
        timer = null;
        fn.apply(this, args);
      }, remaining);
    }
  };

  // 取消功能
  throttled.cancel = function () {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    previous = 0;
  };

  return throttled;
}

使用示例

// 防抖:搜索输入
const handleSearch = debounce((keyword) => {
  console.log('搜索:', keyword);
}, 300);

input.addEventListener('input', (e) => handleSearch(e.target.value));

// 节流:滚动事件
const handleScroll = throttle(() => {
  console.log('滚动位置:', window.scrollY);
}, 200);

window.addEventListener('scroll', handleScroll);

// 取消
handleSearch.cancel();
handleScroll.cancel();

关键点

  • 防抖:延迟执行,重复触发会重置计时器;适用于搜索输入、窗口 resize
  • 节流:固定频率执行,不会重置计时器;适用于滚动事件、按钮点击
  • 立即执行:debounce 的 immediate 参数控制首次是否立即触发
  • leading/trailing:throttle 可配置开始和结束时的执行行为
  • 取消功能:清除定时器并重置状态,防止组件卸载后执行