主题
防抖与节流
防抖和节流都用于处理高频触发的函数调用。它们解决的不是“能不能执行”,而是“应该按什么频率执行”。
两者的核心区别
- 防抖:等一段时间都没有再次触发,才执行一次
- 节流:在一段固定时间内,最多执行一次
可以先记成:
- 防抖更像“等用户停下来”
- 节流更像“控制执行频率”
什么场景适合防抖
更适合“用户停止输入后再处理”的场景:
- 输入框搜索建议
- 窗口尺寸变化后的重新布局
- 表单频繁修改后的自动保存
什么场景适合节流
更适合“持续触发但不希望执行过于频繁”的场景:
- 页面滚动
- 鼠标移动
- 拖拽
- 高频 resize / pointermove 监听
防抖的基本实现
防抖的关键点是:每次触发都取消上一次计时,只保留最后一次。
js
function debounce(fn, delay = 300) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}节流的基本实现
节流的关键点是:在时间窗口内直接忽略后续触发。
js
function throttle(fn, wait = 300) {
let lastTime = 0
return function (...args) {
const now = Date.now()
if (now - lastTime >= wait) {
fn.apply(this, args)
lastTime = now
}
}
}为什么常看到 apply(this, args)
因为包装后的函数如果直接写成 fn(args),很容易把原来的:
this- 参数列表
都处理错。
js
function debounce(fn, delay = 300) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}这能尽量保留调用时的上下文。
节流还有另一类实现
有些节流不是“窗口内完全忽略后续触发”,而是会保留最后一次参数,在窗口结束后补执行一次。
js
function throttle(fn, wait = 300) {
let isWaiting = false
let pendingArgs = null
let pendingThis = null
function later() {
if (!pendingArgs) {
isWaiting = false
return
}
fn.apply(pendingThis, pendingArgs)
pendingArgs = null
pendingThis = null
setTimeout(later, wait)
}
return function (...args) {
if (isWaiting) {
pendingArgs = args
pendingThis = this
return
}
fn.apply(this, args)
isWaiting = true
setTimeout(later, wait)
}
}这种写法更接近“既控频,又尽量不丢最后一次状态”。
两者最容易混淆的点
下面这种需求更偏防抖:
- 用户停止输入 300ms 后再发请求
下面这种需求更偏节流:
- 滚动过程中每 200ms 更新一次位置
如果把两者用反,常见结果是:
- 搜索请求发得过多
- 滚动反馈太迟钝
使用边界
防抖和节流都只是“包装调用频率”,不会改变原函数本身的业务逻辑。
还需要根据场景继续考虑:
- 是否要立即执行第一次
- 是否要保留最后一次调用
- 是否需要取消能力
- 异步请求是否还要结合中断控制
使用建议
- “等停下来再处理”优先想到防抖。
- “持续触发但限频”优先想到节流。
- 包装函数时注意保留
this和参数。 - 需求如果涉及“首尾是否执行”,要先把行为规则说清楚再写代码。
