【前端面试知识点】JavaScript:实现防抖和节流函数,并说明它们的适用场景。

12 阅读3分钟

防抖(Debounce)与节流(Throttle)

防抖和节流是两种用于控制函数执行频率的技术,常用于处理高频触发的事件(如输入、滚动、窗口大小改变等),以优化性能。


一、防抖(Debounce)

定义:在事件被触发后,延迟一段时间执行回调。如果在这段时间内再次触发,则重新计时。只有最后一次触发后的延迟时间结束,才会真正执行。

特点:将多次执行合并为最后一次执行。

实现代码

javascript

function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    // 每次触发都清除之前的定时器
    if (timer) clearTimeout(timer);
    // 设置新的定时器
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null;
    }, delay);
  };
}

使用示例

javascript

const inputHandler = debounce(() => {
  console.log('发送请求');
}, 300);
// 绑定在 input 事件上,用户停止输入 300ms 后才发送请求

适用场景

  • 搜索框输入:用户停止输入后才发起搜索请求,避免每次按键都请求。
  • 窗口大小调整:调整结束后才重新计算布局,避免频繁重排。
  • 表单验证:用户输入完毕后再验证。
  • 按钮重复点击:防止短时间内多次提交。

二、节流(Throttle)

定义:在事件被触发后,每隔一段时间执行一次回调。无论触发多频繁,都保证在规定时间间隔内只执行一次。

特点:将高频触发稀释为每隔固定时间执行一次。

实现代码(时间戳版本)

javascript

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

实现代码(定时器版本,保证最后一次触发也能执行)

javascript

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

更完善的节流(支持首次立即执行和末尾执行)

javascript

function throttle(fn, delay, options = { leading: true, trailing: true }) {
  let timer = null;
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (!lastTime && !options.leading) lastTime = now;
    const remaining = delay - (now - lastTime);
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      lastTime = now;
    } else if (!timer && options.trailing) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        lastTime = options.leading ? Date.now() : 0;
        timer = null;
      }, remaining);
    }
  };
}

使用示例

javascript

const scrollHandler = throttle(() => {
  console.log('滚动位置更新');
}, 100);
// 滚动时每 100ms 执行一次,不会频繁触发

适用场景

  • 滚动加载:滚动到底部加载更多,控制检查频率。
  • 鼠标移动/拖拽:实时更新位置,但避免过于频繁。
  • 窗口滚动监听:如计算滚动位置、导航高亮等。
  • 游戏动画:限制帧率,避免过快执行逻辑。

三、对比与总结

特性防抖(Debounce)节流(Throttle)
执行时机最后一次触发后等待一段时间执行固定时间间隔内最多执行一次
连续触发会不断推迟执行,可能永不执行(若一直触发)按时间间隔稳定执行
典型场景搜索框、窗口 resize滚动、拖拽、鼠标移动
常用选项立即执行版本(leading)首次是否执行(leading)、末尾是否执行(trailing)

选择原则

  • 如果希望停止操作后才执行 → 用防抖。
  • 如果希望持续操作时仍定期执行 → 用节流。

四、注意事项

  1. 防抖和节流函数返回的新函数,其 this 需要正确绑定,实现中已使用 apply 保留。
  2. 定时器需要及时清除,避免内存泄漏。
  3. 实际开发中,可借助 lodash 等库的 _.debounce 和 _.throttle,但手写实现有助于理解原理。