防抖与节流

1. 是什么?

本质上都是优化高频率执行代码的一种手段。

如浏览器的 resize、scroll、keypress、mousemove 等事件在触发时,会不断调用绑定在事件上的回调函数,极大地浪费资源、降低前端性能。

为了优化体验,需要对这类事件进行调用次数的限制,对此我们可以采用 throttle(节流) 和 debounce(防抖) 的方式来减少调用频率。

  • 防抖(debounce):触发事件后 n 秒内不再触发才执行;若持续触发则一直延迟。常见场景:搜索框输入、按钮防重复提交、resize 后重绘。
  • 节流(throttle):保证 n 秒内只执行一次。常见场景:滚动加载、动画帧、射击游戏中的子弹频率。

2. 实现

防抖(debounce)

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

节流(throttle)—— 时间戳版本

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

节流(throttle)—— 定时器版本

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

精确节流:结合写法

function throttled(fn, delay) {
  let timer = null;
  let oldTime = Date.now();
  return function (...args) {
    const curTime = Date.now();
    const remaining = delay - (curTime - oldTime);
    const context = this;
    clearTimeout(timer);
    if (remaining <= 0) {
      fn.apply(context, args);
      oldTime = Date.now();
    } else {
      timer = setTimeout(() => fn.apply(context, args), remaining);
    }
  };
}

3. 使用场景

  • 防抖:搜索联想、表单校验、按钮防重、resize 结束后的重绘。
  • 节流:滚动加载、动画帧、滚动事件、拖拽场景、高频鼠标移动。

补充:注意区分:

  • lodash 的 debounce / throttle 提供更多可选项(leading、trailing、maxWait)。
  • 现代前端也可以用 requestAnimationFrame 来做"帧级节流",常用于滚动 / 动画场景。
  • 千万不要把防抖和节流混用,行为差别很大。

来源整理自:我的有道云笔记