使用 JavaScript 进行开发时,都会使用到事件,有一些事件我们只想触发一次,但却频繁触发多次,例如:
window
的resize
、scroll
mousedown
、mousemove
keyup
、keydown
- ...
我们可能会在其中执行请求,或处理一些复杂的逻辑,这样一次性触发多次可能会造成资源浪费。
有两种方法可以控制事件触发后,回调函数的调用频率,即节流和防抖。
防抖(debounce)是指在一段时间内最多调用一次,即如果在规定时间内再次调用该函数,则会将原定时器清除并重新计时。
常规防抖实现如下:
function debounce(fn, wait) {
let timer = null
return function () {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, arguments)
}, wait)
// 方便在外部能够清除计时器
return timer
}
}
这个函数接受两个参数:
fn
— 需要进行防抖的函数。wait
— 在调用fn
函数之前需要等待的时间(以毫秒为单位)。
然后它返回一个包装函数,该函数会将原定时器清除并重新计时。在规定时间内,如果再次调用包装函数,则会将原定时器清除并重新计时。
另外,你可以进一步改进该防抖函数,例如添加一个可选参数,使包装函数能够立即调用 fn
函数。这里我们不在介绍,详细内容可以在最后提供的资料中找到答案。
查看是否触底,常用于触底加载更多数据:
const isAtBottom = function () {
console.log(
document.documentElement.clientHeight + window.scrollY >=
document.documentElement.scrollHeight
)
}
document.addEventListener('scroll', debounce(isAtBottom, 1000))
节流(throttle)是指在一段时间内,只允许函数被调用一次。
与防抖不同,节流函数在等待期间不会重置计时器。如果在等待期间多次调用函数,只有第一次会被执行,其他调用会被忽略。
有两种实现方式:时间戳和定时器。
function throttle(fn, wait) {
let previous = 0
return function () {
const now = Date.now()
if (now - previous > wait) {
fn.apply(this, arguments)
previous = now
}
return fn
}
}
它返回一个包装函数,该函数会检查与上一次调用之间是否已经过了规定的时间。如果是,则调用 fn
函数并更新上一次调用的时间。否则,不会调用 fn
函数。
function throttle(fn, wait) {
let timer = null
return function () {
if (timer) return
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, wait)
return timer
}
}
它返回一个包装函数,该函数会在第一次调用时设置一个定时器。在等待期间,如果再次调用该函数,则不会执行任何操作。在定时器到期后,会调用 fn
函数并清除定时器。
两者的区别在于,函数节流是在一段时间内最多调用一次,而函数防抖是在一段时间内只调用一次。
一段时间内最多调用一次意味着,如果你在这段时间内多次调用函数,那么只有第一次调用会生效,之后的调用都会被忽略。
一段时间内只调用一次意味着,如果你在这段时间内多次调用函数,那么只有最后一次调用会在这段时间结束后生效,之前的调用都会被忽略。
函数节流适用于需要限制调用频率的场景,而函数防抖适用于需要等待用户输入完成后才进行响应的场景。
本文我们实现了基本够用的节流和防抖,你还可以添加 lodash 这类工具库提供对节流和防抖的各种功能处理,以适用各种项目。
更多详细内容可以阅读以下文章: