一句话:节流是「隔一段时间才准执行一次」,防抖是「停下来才执行」——滚动用节流,搜索输入用防抖。
高频事件(scroll、resize、input)若每次触发都请求接口或重算布局,会拖垮性能。 节流(throttle) 和 防抖(debounce) 是前端最常用的两种包装策略。二者区别常被记混:核心在于 首次是否立即执行 以及 连续触发时的计时逻辑。
语义:在固定时间间隔内,无论触发多少次,函数最多执行一次。
典型场景:页面滚动加载、鼠标移动跟踪、按钮防重复点击、拖拽。
时间戳版(首次立即执行,结束边界可能多触发一次):
export function throttle(fn, delay) {
let lastTime = 0
return function (...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}
定时器版(首次也受 delay 约束,结束更均匀):
export function throttle(fn, delay = 500) {
let flag = true
return function (...args) {
if (flag) {
fn.apply(this, args)
flag = false
setTimeout(() => { flag = true }, delay)
}
}
}
使用示例:
const throttledScroll = throttle(() => {
console.log('滚动中...')
}, 2000)
window.addEventListener('scroll', throttledScroll)
语义:连续触发时只认「最后一次」;每次触发都重置等待计时,静默 delay 毫秒后才真正执行。
典型场景:搜索框联想、窗口 resize 结束后重绘、表单校验。
export function debounce(fn, delay) {
let timerId = null
return function (...args) {
if (timerId) clearTimeout(timerId)
timerId = setTimeout(() => {
fn.apply(this, args)
timerId = null
}, delay)
}
}
const debouncedInput = debounce(() => {
console.log('输入稳定,发起搜索')
}, 500)
document.getElementById('inputField').addEventListener('input', debouncedInput)
leading 防抖(可选):第一次立即执行,后续连续输入仍合并——lodash 的 debounce(fn, wait, { leading: true, trailing: false }) 即此类。
| 维度 | 节流 | 防抖 |
|---|---|---|
| 连续触发 | 按固定间隔执行 | 重新计时,静默后才执行 |
| 首次触发 | 时间戳版立即执行 | 默认延迟后执行 |
| 比喻 | 地铁发车间隔 | 电梯等人齐再关门 |
| 场景 | scroll、mousemove | input 搜索、resize |
sequenceDiagram
participant E as 事件
participant T as 节流
participant D as 防抖
Note over E,T: 每 200ms 最多 1 次
E->>T: 触发×10
T->>T: 执行约 5 次
Note over E,D: 停止输入 500ms 后 1 次
E->>D: 触发×10
D->>D: 最终执行 1 次
useDebounceFn、useThrottleFndebounce、throttle(注意 throttle 已标记 deprecated,新项目可自实现或换 @vueuse/core)在线对比 Demo:jiang-xia.top/tool/test
fn.apply(this, args) 保留调用上下文。onUnmounted 里 clearTimeout,或取消防抖函数的 pending 调用。requestAnimationFrame 配合时 scroll 动画更顺滑。apply(this, args) 与卸载清理。