🎓 作者简介: 前端领域优质创作者
🚪 资源导航: 传送门=>
🎬 个人主页: 江城开朗的豌豆
🌐 个人网站: 江城开朗的豌豆 🌍
📧 个人邮箱: 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));
应用场景
- 搜索框输入联想
- 窗口大小调整(resize)事件
- 表单验证(等用户输入完成再验证)
节流(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);
}
};
}
应用场景
- 页面滚动加载更多(无限滚动)
- 鼠标移动事件(mousemove)
- 频繁点击按钮的提交
两种实现的区别
| 特性 | 防抖(debounce) | 节流(throttle) |
|---|---|---|
| 执行时机 | 事件停止触发后执行 | 固定时间间隔执行 |
| 是否保证执行 | 不保证(如果一直触发就永远不会执行) | 保证在一定时间内至少执行一次 |
| 适用场景 | 搜索联想、resize事件 | 滚动事件、动画 |
进阶技巧:结合使用
有些场景可能需要结合两者使用。比如实时搜索时:
- 先用防抖避免频繁请求(比如延迟500ms)
- 但同时用节流保证至少每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);
}
};
}
实际项目中的使用建议
- 合理设置时间参数:防抖一般100-500ms,节流根据场景可能需要16ms(60fps)到几百毫秒不等
- 考虑leading和trailing:有些场景可能需要在开始时立即执行一次(leading),然后节流/防抖后续调用
- 取消功能:实现cancel方法可以取消尚未执行的调用
- 返回值处理:对于有返回值的函数,可能需要特殊处理(如Promise)
现代前端框架中的使用
现在很多工具库已经提供了这些功能的实现:
- Lodash:
_.debounce和_.throttle - RxJS:
debounceTime和throttleTime操作符 - Vue: 可以直接在@事件中使用修饰符
<input @input.debounce="search"> - React: 可以使用hooks封装
总结
防抖和节流是前端性能优化的必备技能。记住:
- 防抖适合"最终状态"场景(如搜索输入完成)
- 节流适合"过程状态"场景(如持续滚动)
希望这篇文章能帮助你理解这两个概念的区别和应用。下次遇到高频事件处理时,不妨想想:"这里需要防抖还是节流?"。
如果你有更多使用心得或问题,欢迎在评论区分享交流!