你在搜索框打字,每打一个字母就发一次请求?打"hello"五个字母就发五次?后端要被你打炸了。防抖和节流就是用来给高频事件"踩刹车"的——不让它那么疯狂地触发。
一句话区分:防抖 = 停了才执行最后一次(你停笔了才查);节流 = 固定间隔执行一次(每隔固定时间查一次)。选错了,要么接口请求爆炸,要么体验卡顿。
打个比方:你在电梯里按关门,每按一次它就重新开门等你——只要你还在按,它就不关。防抖就是这个思路:只要事件还在触发,就不执行,重新计时;你停下来了,它才执行最后一次。
// 防抖函数:把你的业务函数包一层,返回一个新的"带刹车"的函数
function debounce(fn, delay = 300) {
let timer = null; // 存定时器的编号
// 返回一个新函数:以后调这个新函数,就有了防抖效果
return function (...args) {
clearTimeout(timer); // ★ 每次触发先把上一次的定时器取消!
// 就像电梯门刚要关,你又按了一下,重新等
timer = setTimeout(() => { // 重新开一个定时器,延迟 delay 毫秒后执行(箭头函数要写 ())
fn.apply(this, args); // 到时间了,真正执行业务函数
timer = null;
}, delay);
};
}
// 用法:
function searchHandler(val) {
console.log("搜索了:" + val); // 实际开发这里发 AJAX 请求
}
// 把 searchHandler 包成防抖版,300 毫秒内连续触发只执行最后一次
const debouncedSearch = debounce(searchHandler, 300);
// 输入框绑定防抖版,快速打"hello"只发一次请求
document.getElementById("inpSearch").oninput = function () { // 注意 id 要和 HTML 里的一致
debouncedSearch(this.value);
};
如果你在输入框直接绑 searchHandler,打五个字母发五次请求——因为没有刹车,每按一个键就触发一次。用防抖包一层后,你停下来 300 毫秒才发一次,接口压力小多了。
节流的思路不同:不管你触发多频繁,我每隔固定时间只执行一次。就像闹钟,你不按它也会每隔固定时间响一次——不是"停了才响",而是"每隔一段时间响一次"。
// ===== 版本一:时间戳版(第一次立刻执行) =====
function throttle(fn, interval = 300) {
let lastTime = 0; // 上一次执行的时间
return function (...args) {
const now = Date.now(); // 现在的时间戳
// 如果距上次执行够间隔了,就执行
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now; // 更新上次执行时间
}
};
}
// ===== 版本二:定时器版(第一次延迟执行) =====
function throttleTimer(fn, interval = 300) {
let timer = null;
return function (...args) {
if (timer) return; // 已经有定时器在排队了,就不动
timer = setTimeout(() => { // 箭头函数要写 ()
fn.apply(this, args);
timer = null; // 执行完了,解锁
}, interval);
};
}
两个版本区别:时间戳版——第一次触发立刻执行,停止后不再执行最后一次;定时器版——第一次延迟执行,停止后还会执行最后一次。看场景选。
| 对比维度 | 防抖 debounce | 节流 throttle |
|---|---|---|
| 核心逻辑 | 频繁触发重置计时,停了才执行 | 固定间隔强制执行一次 |
| 执行时机 | 停止触发后延迟执行(延后) | 时间戳版:首次立即;定时器版:延后 |
| 触发频率 | 只执行最后一次 | 匀速分段执行 |
| 典型效果 | 输入停半秒才搜索 | 滚动时每隔一段距离加载 |
| 关键词 | clearTimeout 重置定时器 | 记录时间戳 / 锁住定时器 |
// 1. 输入框实时联想搜索 → 用防抖! // 你打字时不需要每打一个字母就搜一次,停下来再搜 // 减少接口请求次数,用户体验也好 // 2. 窗口缩放 resize → 用防抖! // 你拖窗口边框时会疯狂触发 resize,松手了再重新算布局 // 3. 页面滚动下拉加载 → 用节流! // 滚动是持续触发的,你需要每隔固定时间检测一次位置 // 不能等用户停了再加载——那样用户滚到中间啥都没有 // 4. 提交按钮防重复点击 → 用节流/短时防抖! // 用户快速点好几下提交,只发一次请求,防止提交多条数据
口诀:只需要结束后执行一次 → 防抖;需要过程中均匀执行 → 节流。
可能在什么地方用:
① 搜索框输入联想(防抖);② 窗口拖动缩放重新布局(防抖);③ 页面滚动到底自动加载更多(节流);④ 按钮防重复提交(节流/防抖)。
常见的问题:
① 防抖里忘了 clearTimeout,导致每次还是都执行;② 节流用在搜索框上——刚打第一个字就立刻发了请求,用户还没打完就白跑一趟;③ 防抖用在滚动加载上——结果用户不滚了才加载,体验很差;④ 延迟时间设太长,用户等了一秒多才出结果。
解决思路:
① 防抖核心就是 clearTimeout(timer) 重置计时;② 搜索框用防抖(300ms 合适);③ 滚动加载用节流(200~500ms 合适);④ 按钮防重可以在提交期间把按钮 disabled。
练习一:防抖测试——① 封装 debounce 函数;② 做一个输入框,分别测试不加防抖和加防抖(300ms)时快速输入"hello"的打印次数差异。
练习二:节流测试——① 封装 throttle 函数;② 做一个 div,绑 mousemove 事件,分别测试不加节流和加节流(100ms)时鼠标移动的打印频率差异。
练习四:按钮防重复提交——创建一个提交按钮,点击时 setTimeout 模拟 2 秒提交。快速点好几次,看发了几次请求。再用防抖/节流改造,对比哪种更合适。