JavaScript篇:前端性能优化:手把手教你实现函数节流与防抖

353 阅读5分钟

🎓 作者简介前端领域优质创作者

🚪 资源导航: 传送门=>

🎬 个人主页:  江城开朗的豌豆

🌐 个人网站:    江城开朗的豌豆 🌍

📧 个人邮箱: YANG_TAO_WEB@163.com 📩

💬 个人微信:     y_t_t_t_ 📱

📌  座  右 铭: 生活就像心电图,一帆风顺就证明你挂了 💔

👥 QQ群:  906392632 (前端技术交流群) 💬

作为前端开发者,我们经常需要处理用户频繁触发的事件,比如窗口滚动、输入框输入、按钮点击等。如果不加控制,这些高频事件可能会导致性能问题甚至页面卡顿。今天我就来分享两个非常实用的技巧——函数节流(throttle)和防抖(debounce),它们能有效优化这类场景的性能表现。

为什么需要节流和防抖?

记得我刚入行时,接到一个需求:在搜索框输入时实时展示搜索结果。我直接给输入框绑定了oninput事件,结果每次输入都会触发搜索请求。当用户快速输入"hello"时,竟然发送了5次请求!这不仅浪费服务器资源,还可能导致结果展示错乱。

后来我的导师告诉我:"这种情况你需要用防抖或节流来优化"。经过学习和实践,我终于理解了它们的区别和应用场景。下面我就用通俗易懂的方式分享给大家。

防抖(debounce):等你说完我再响应

防抖的核心思想是:在事件被触发后,等待一段时间再执行回调。如果在这段时间内事件又被触发,则重新计时。

生活场景比喻

想象一下,我是一名客服,用户可以通过聊天窗口向我提问。如果用户每输入一个字就发送一次消息(就像实时搜索那样),我会被频繁打断。更合理的做法是:当用户停止输入一段时间(比如3秒)后,我再一次性处理完整的消息。

代码实现

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

// 使用示例
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function(e) {
  console.log('我发送搜索请求:', e.target.value);
  // 实际这里会发起AJAX请求
}, 500));

应用场景

  1. 搜索框输入联想
  2. 窗口大小调整(resize)事件
  3. 表单验证(等用户输入完成再验证)

节流(throttle):固定频率响应

节流的原理是:在一定时间间隔内,只执行一次回调。不管事件触发有多频繁,回调都会按照固定的时间间隔执行。

生活场景比喻

这次我是一名电梯管理员。电梯运行需要时间,不能每按一次按钮就立即响应。我设定电梯每10秒检查一次请求,在这期间的所有按钮按下都只会在下一个10秒周期响应一次。

代码实现

function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    // 如果距离上次执行时间小于间隔,就忽略
    if (now - lastTime < interval) return;
    lastTime = now;
    fn.apply(this, args);
  };
}

// 使用示例
window.addEventListener('scroll', throttle(function() {
  console.log('我处理滚动事件');
  // 实际可能是懒加载图片等操作
}, 1000));

定时器版本实现

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

应用场景

  1. 页面滚动加载更多(无限滚动)
  2. 鼠标移动事件(mousemove)
  3. 频繁点击按钮的提交

两种实现的区别

特性防抖(debounce)节流(throttle)
执行时机事件停止触发后执行固定时间间隔执行
是否保证执行不保证(如果一直触发就永远不会执行)保证在一定时间内至少执行一次
适用场景搜索联想、resize事件滚动事件、动画

进阶技巧:结合使用

有些场景可能需要结合两者使用。比如实时搜索时:

  1. 先用防抖避免频繁请求(比如延迟500ms)
  2. 但同时用节流保证至少每2秒请求一次(防止用户持续输入导致长时间不发送请求)
function enhancedDebounce(fn, delay, maxWait) {
  let timer = null;
  let lastInvokeTime = 0;
  
  return function(...args) {
    const now = Date.now();
    clearTimeout(timer);
    
    // 如果超过最大等待时间,立即执行
    if (maxWait && now - lastInvokeTime >= maxWait) {
      fn.apply(this, args);
      lastInvokeTime = now;
    } else {
      timer = setTimeout(() => {
        fn.apply(this, args);
        lastInvokeTime = Date.now();
      }, delay);
    }
  };
}

实际项目中的使用建议

  1. 合理设置时间参数:防抖一般100-500ms,节流根据场景可能需要16ms(60fps)到几百毫秒不等
  2. 考虑leading和trailing:有些场景可能需要在开始时立即执行一次(leading),然后节流/防抖后续调用
  3. 取消功能:实现cancel方法可以取消尚未执行的调用
  4. 返回值处理:对于有返回值的函数,可能需要特殊处理(如Promise)

现代前端框架中的使用

现在很多工具库已经提供了这些功能的实现:

  • Lodash: _.debounce 和 _.throttle
  • RxJS: debounceTime 和 throttleTime 操作符
  • Vue: 可以直接在@事件中使用修饰符 <input @input.debounce="search">
  • React: 可以使用hooks封装

总结

防抖和节流是前端性能优化的必备技能。记住:

  • 防抖适合"最终状态"场景(如搜索输入完成)
  • 节流适合"过程状态"场景(如持续滚动)

希望这篇文章能帮助你理解这两个概念的区别和应用。下次遇到高频事件处理时,不妨想想:"这里需要防抖还是节流?"。

如果你有更多使用心得或问题,欢迎在评论区分享交流!