防抖和节流是 JavaScript 性能优化里非常常见的两个概念。它们都用来控制函数执行频率,但适用场景不同。搜索框输入、窗口缩放、滚动监听、按钮点击、接口请求,都可能用到它们。
很多人能背出“防抖是最后执行,节流是间隔执行”,但真正写业务时仍然容易选错。本文从实际场景出发,讲清防抖和节流的区别、实现思路和常见用法。
为什么需要控制频率
浏览器事件触发频率可能很高。用户输入搜索词时,每按一个字都会触发 input;页面滚动时,scroll 事件可能在短时间内触发很多次;窗口缩放也会连续触发 resize。
如果每次事件都立刻执行复杂计算或发送接口请求,页面可能卡顿,服务端也会收到大量无效请求。防抖和节流就是用来减少这类无意义执行。
防抖是什么
防抖的核心是:连续触发时先不执行,等停止触发一段时间后再执行。它适合“用户操作结束后再处理”的场景,比如搜索输入、表单校验、窗口缩放结束后重新计算布局。
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
每次触发都会清除上一次定时器,重新计时。只有用户停止触发超过指定时间,函数才会真正执行。

搜索输入防抖
搜索框是防抖最典型的场景。用户输入过程中不必每个字符都请求接口,可以等用户停顿后再搜索。
const search = debounce((keyword) => {
console.log('请求搜索接口:', keyword);
}, 500);
input.addEventListener('input', (event) => {
search(event.target.value);
});
这样可以减少接口请求次数,也能避免搜索结果频繁闪动。延迟时间通常可以设置在 300 到 600 毫秒之间,具体看交互需求。
立即执行防抖
有些场景希望第一次触发立刻执行,后续连续触发被忽略,直到一段时间后才能再次触发。这可以看作立即执行版本的防抖。
function debounceImmediate(fn, delay) {
let timer = null;
return function (...args) {
const shouldRun = !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
}, delay);
if (shouldRun) fn.apply(this, args);
};
}
它常用于防止按钮短时间内重复点击。但对于真正的提交按钮,还应该配合 loading 状态和后端幂等处理,不能只依赖前端防抖。
节流是什么
节流的核心是:持续触发时,按固定间隔执行。它适合“事件一直发生,但只需要定期处理”的场景,比如滚动监听、拖拽、窗口滚动进度、页面曝光统计等。
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
fn.apply(this, args);
}
};
}
这段实现基于时间戳。只要距离上一次执行超过指定间隔,就允许执行一次。
滚动监听节流
滚动监听是节流的典型场景。比如计算页面滚动进度,不需要每一次 scroll 都更新,只要每隔一段时间处理一次即可。
const onScroll = throttle(() => {
console.log('处理滚动位置:', window.scrollY);
}, 200);
window.addEventListener('scroll', onScroll);
节流可以减少高频事件带来的性能压力。滚动相关逻辑还要尽量避免复杂 DOM 查询和强制同步布局。
定时器节流
节流也可以用定时器实现。时间戳版本通常会立即执行第一次,定时器版本更容易控制最后一次是否补执行。
function throttleTimer(fn, delay) {
let timer = null;
return function (...args) {
if (timer) return;
timer = setTimeout(() => {
timer = null;
fn.apply(this, args);
}, delay);
};
}
实际项目里可以根据需求选择实现方式。如果需要更完整的 leading、trailing 控制,可以使用成熟工具函数或自己封装配置项。
按钮防重复点击
按钮重复点击可以用立即防抖,也可以用禁用按钮状态。更推荐业务上使用 loading 或 disabled 状态,让用户知道请求正在处理。
button.disabled = true;
submitForm().finally(() => {
button.disabled = false;
});
前端控制只能减少误操作,后端仍然要处理重复提交问题。比如订单支付、表单提交、创建资源等关键操作,应该设计幂等机制。
怎么选择
简单判断:只关心用户停止后的最后一次结果,用防抖;需要持续过程中按频率更新,用节流。搜索输入用防抖,滚动进度用节流;窗口 resize 后重新布局通常用防抖,滚动曝光统计通常用节流。
如果你希望用户连续操作时只处理最终状态,防抖更合适;如果你希望过程中持续有反馈,但不要太频繁,节流更合适。
常见错误
第一种错误是把搜索输入做成节流,导致用户还没输入完就频繁请求。第二种错误是把滚动监听做成防抖,导致滚动过程中没有任何反馈。第三种错误是每次事件触发时重新创建防抖函数,导致防抖失效。
错误思路:事件里临时创建 debounce
正确思路:先创建包装函数,再绑定事件
还有一种错误是只做前端防重复提交,后端没有兜底。涉及金钱、订单、账号、权限的操作,后端必须保证安全。
实践建议
写防抖和节流时,先明确你要控制的是“最终结果”还是“执行频率”。搜索、校验、resize 结束处理,多数用防抖;滚动、拖拽、持续进度更新,多数用节流;提交按钮则要结合禁用状态和后端幂等。
防抖和节流不是为了炫技,而是为了让页面更稳、请求更少、交互更清晰。掌握它们之后,很多高频事件场景都会更容易处理。














暂无评论内容