前置知识: JavaScript

防抖与节流

2 minIntermediate2026/6/14

JavaScript防抖(debounce)与节流(throttle)详解:原理、实现与实际应用。

1. 防抖(Debounce)

1.1 原理

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

触发: ─A─B─C───────D──────→
执行: ─────────────────E──→  (D 之后等待 delay 才执行)

1.2 基本实现

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

1.3 支持立即执行

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

  return function (...args) {
    if (timer) clearTimeout(timer);

    if (immediate && !timer) {
      fn.apply(this, args);
    }

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

1.4 支持取消和取值

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

  const debounced = function (...args) {
    if (timer) clearTimeout(timer);

    if (immediate && !timer) {
      fn.apply(this, args);
    }

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

  debounced.cancel = function () {
    clearTimeout(timer);
    timer = null;
  };

  debounced.flush = function (...args) {
    debounced.cancel();
    fn.apply(this, args);
  };

  return debounced;
}

1.5 应用场景

// 搜索输入
const searchInput = document.getElementById('search');
searchInput.addEventListener(
  'input',
  debounce(async (e) => {
    const results = await fetchSuggestions(e.target.value);
    renderSuggestions(results);
  }, 300)
);

// 窗口调整
window.addEventListener(
  'resize',
  debounce(() => {
    recalculateLayout();
  }, 250)
);

// 表单自动保存
editor.addEventListener(
  'input',
  debounce(() => {
    saveDraft();
  }, 1000)
);

2. 节流(Throttle)

2.1 原理

节流:在指定时间间隔内最多执行一次,不管触发多少次。

触发: ─A─B─C──D──E─F─G─→
执行: ─A───────D───────G→  (固定间隔执行)

2.2 时间戳实现

function throttle(fn, interval) {
  let lastTime = 0;

  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}

缺点:最后一次触发如果不在间隔内,会被忽略。

2.3 定时器实现

function throttle(fn, interval) {
  let timer = null;

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

缺点:第一次触发需要等待 interval 才执行。

2.4 结合实现(首尾都执行)

function throttle(fn, interval) {
  let lastTime = 0;
  let timer = null;

  return function (...args) {
    const now = Date.now();
    const remaining = interval - (now - lastTime);

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

2.5 支持取消

function throttle(fn, interval) {
  let lastTime = 0;
  let timer = null;

  const throttled = function (...args) {
    const now = Date.now();
    const remaining = interval - (now - lastTime);

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

  throttled.cancel = function () {
    clearTimeout(timer);
    timer = null;
    lastTime = 0;
  };

  return throttled;
}

2.6 应用场景

// 滚动事件
window.addEventListener(
  'scroll',
  throttle(() => {
    updateScrollPosition();
    lazyLoadImages();
  }, 100)
);

// 鼠标移动
canvas.addEventListener(
  'mousemove',
  throttle((e) => {
    drawTrail(e.clientX, e.clientY);
  }, 16)
); // ~60fps

// 按钮防重复点击
submitBtn.addEventListener(
  'click',
  throttle(async () => {
    await submitForm();
  }, 2000)
);

3. 对比总结

维度防抖(Debounce)节流(Throttle)
执行时机停止触发后执行固定间隔执行
触发频率最后一次触发才执行间隔内最多执行一次
首次执行可配置 immediate可配置 leading
末次执行总是执行可配置 trailing
典型场景搜索输入、自动保存滚动、拖拽、resize

4. requestAnimationFrame 节流

// 适用于动画/视觉更新场景
function rafThrottle(fn) {
  let rafId = null;

  return function (...args) {
    if (rafId !== null) return;

    rafId = requestAnimationFrame(() => {
      fn.apply(this, args);
      rafId = null;
    });
  };
}

// 使用
element.addEventListener(
  'mousemove',
  rafThrottle((e) => {
    updatePosition(e.clientX, e.clientY);
  })
);