说到防抖,想必多数人首先想到的是相机的防抖。因为我们并不是机器人,所以拿手机拍照的时候,手都会有不易察觉的抖动,这样的抖动会影响相片的质量。手机对这些情况做的一些补偿操作,减小了手抖对成像造成的影响。
我们都知道,JavaScript 是一门编程语言,不是人类也不是机器人。那什么情况下,会产生“抖动”呢?
场景
联想一个平平无奇的登录框,当用户信息输入完毕,点击登录按钮,可能网速有点慢还是啥的,用户等得不耐烦,不停点击,导致鼠标患上帕金森,登录按钮就被一次一次地点击,前端不停地向后台发送重复的请求。
如下面的例子(这里点击一次,执行 console.log('click')
, 并且用 console.log('submit')
代指请求):
可以看到,短时间内连续点击,每次点击都会触发请求.
这种情况,就属于“抖动”。
服务器接收到这样的请求,肯定是一脸懵啊,这谁顶得住?
这个时候,就需要像手机相机一样,做一些操作,减少鼠标抖动对网络请求的影响,减轻服务器的压力。
怎么做
“抖动”情景下,多次点击,导致发送了多次一样的请求。函数防抖的处理方式是:先规定一个时间段,比如一秒,点击按钮,一秒之后再发送请求,假如一秒内又产生了一次点击,那么重新计时,点击过后一秒再发送请求。
这样一来,规定时间段内的快速点击,只会产生一次请求。不管打字多快的手速,也战胜不了防抖的函数。
怎么写
直接上代码:
const debounce = (func, delay = 200) => {
let timeout = null
return function () {
clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(this, arguments)
}, delay)
}
}
复制代码
debounce
函数接受一个函数 func
和一个默认为 200 毫秒延迟时间 delay
作为参数。返回一个函数,触发返回的函数,开始计时,delay
毫秒后触发 func
, 假如 delay
时间段内,再次触发这个函数,那么重新计时,delay
毫秒后触发 func
.
debounce
首先声明变量 timeout
, 用于存放之后 setTimeout
函数返回的定时器编号。
然后返回一个函数,函数内执行 clearTimeout
来依据先前声明的 timeout
来清除定时器。当然,一开始,传入的 timeout
值为 null
, 这时的清除操作忽略不计。
接着,执行 setTimeout
, 在至少 delay
规定的毫秒后,将 setTimeout
的回调函数添加到当前事件队列,回调内执行 func
函数。并且把返回的定时器编号赋值给 timeout
, 这样,下一次触发 debounce
返回的函数时,就可以清除通过上面的 clearTimeout(timeout)
来清除定时器 。
注意到上面执行 func
用的是 func.apply(this, arguments)
, 这样一来,就可以对 debounce
返回的那个函数传递参数,func
执行的时候,再把参数传给 func
.
来用一下:
const submit = () => {
console.log('submit')
}
const debounceSubmit = debounce(submit, 500)
let btnSubmit = document.getElementById('submit')
btnSubmit.addEventListener('click', () => {
console.log('click')
debounceSubmit()
})
复制代码
这里将 submit
函数传入 debounce
函数,并设置延迟时间为 500 毫秒。 debounce
返回的函数赋给 debounceSubmit
, 然后在提交按钮 btnSubmit
的点击事件回调中执行 debounceSubmit
.
看下效果:
上图中,一开始的几次连续点击,都不会触发 submit
,停止点击后,才触发了一次 submit
. 之后两次有一定间隔时间的点击,都触发了 submit
.
总结
函数的防抖将一定时间内的多次操作,减少为一次,去除冗余,节约资源。