外观
怎么实现防抖和节流?
防抖:事件停下来,再等一段时间,才执行一次。节流:一段时间里最多执行一次。搜索输入用防抖,滚动和缩放用节流。两个都要能取消,并保留调用时的 this 和参数。
原理
防抖每次触发都清掉上一个定时器。只有最后一次触发之后再等满 wait 毫秒,回调才运行。
js
function debounce(fn, wait) {
let timer = null
function debounced(...args) {
const context = this
clearTimeout(timer)
timer = setTimeout(() => {
timer = null
fn.apply(context, args)
}, wait)
}
debounced.cancel = () => {
clearTimeout(timer)
timer = null
}
return debounced
}节流记录上次执行的时间。间隔已经够了就立刻执行;还没够就预约一次尾部调用,保证最后一次变化不会丢。
js
function throttle(fn, wait) {
let last = 0
let timer = null
function throttled(...args) {
const context = this
const now = Date.now()
const remaining = wait - (now - last)
if (remaining <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}
last = now
fn.apply(context, args)
return
}
if (!timer) {
timer = setTimeout(() => {
last = Date.now()
timer = null
fn.apply(context, args)
}, remaining)
}
}
throttled.cancel = () => {
clearTimeout(timer)
timer = null
last = 0
}
return throttled
}上面的节流是「首部加尾部」:第一次马上执行,停止触发前如果还有没执行的变化,再补一次。只要首部时,把 setTimeout 那支去掉;只要尾部时,第一次也不要立刻执行。
追问
按钮点击适合防抖吗?
提交按钮更适合「进行中就忽略下一次」,也就是加锁,而不是等用户停止点击。防抖会让第一次点击也推迟。搜索框才需要等输入停下来。
和 requestAnimationFrame 怎么选?
跟帧走的视觉更新用 requestAnimationFrame,一帧最多一次。跟网络请求走的输入用定时器防抖,等待时间由产品决定,不由刷新率决定。
易错点
- 防抖函数在定义时创建一次。写在渲染函数里每次都重新
debounce(...),定时器永远攒不起来。 - 组件卸载或页面离开时调用
cancel,否则回调还会改已经卸掉的状态。 - 箭头函数包一层时,里面的
this不是事件目标。要this就使用普通函数,或者显式传入元素。