5、✅ 手写防抖(debounce)与节流(throttle)函数

105 阅读2分钟

🎯 一、为什么要掌握防抖与节流?

它们是前端性能优化的“老三样”之一,常用于:

  • **防止频繁触发:**输入框搜索、窗口 resize、滚动监听
  • 面试必问场景题:“你怎么优化滚动事件监听?”

🧠 二、概念区别一张图看懂

特性防抖(debounce)节流(throttle)
执行时机停止一段时间后执行一次固定时间间隔内最多执行一次
场景搜索框输入、按钮防重复提交页面滚动、resize、mousemove

✍️ 三、手写 debounce(防抖)

✅ 实现 1:基础版(触发后延迟执行)

function debounce(fn, delay = 300) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

✅ 示例使用

window.addEventListener('resize', debounce(() => {
  console.log('窗口变化');
}, 500));

✅ 实现 2:立即执行版(leading = true)

function debounce(fn, delay = 300, immediate = false) {
  let timer = null;
  return function (...args) {
    const isFirst = immediate && !timer;
    clearTimeout(timer);

    timer = setTimeout(() => {
      timer = null;
      if (!immediate) fn.apply(this, args);
    }, delay);

    if (isFirst) fn.apply(this, args);
  };
}

✍️ 四、手写 throttle(节流)

✅ 实现 1:时间戳版(立即执行)

function throttle(fn, wait = 300) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= wait) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

✅ 实现 2:定时器版(延迟执行)

function throttle(fn, wait = 300) {
  let timer = null;
  return function (...args) {
    if (timer) return;
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null;
    }, wait);
  };
}

✅ 实现 3:混合版(可配置 leading 和 trailing)

function throttle(fn, wait = 300, { leading = true, trailing = true } = {}) {
  let timer = null;
  let lastTime = 0;

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

    if (!leading && lastTime === 0) {
      lastTime = now;
    }

    const remaining = wait - (now - lastTime);

    if (remaining <= 0) {
      clearTimeout(timer);
      timer = null;
      lastTime = now;
      fn.apply(this, args);
    } else if (!timer && trailing) {
      timer = setTimeout(() => {
        lastTime = leading ? Date.now() : 0;
        timer = null;
        fn.apply(this, args);
      }, remaining);
    }
  };
}

✅ 五、验证用例(滚动监听)

window.addEventListener('scroll', throttle(() => {
  console.log('页面滚动');
}, 1000));

❗ 六、常见面试陷阱

问题答案
防抖能否立即执行?可以,加 immediate 参数
节流 trailing 和 leading 如何实现?控制时间戳/定时器配合
节流和防抖能否合并?理论上不建议,但可以写工具库支持模式切换
thisarguments 会丢失吗?是的,需用 apply(this, args) 绑定上下文

📘 七、延伸思考(面试加分)

  • lodash.debouncelodash.throttle 内部实现有何异同?
  • 如何封装一个 Vue/React 通用指令或 Hook 实现?