说到防抖,想必多数人首先想到的是相机的防抖。因为我们并不是机器人,所以拿手机拍照的时候,手都会有不易察觉的抖动,这样的抖动会影响相片的质量。手机对这些情况做的一些补偿操作,减小了手抖对成像造成的影响。
我们都知道,JavaScript 是一门编程语言,不是人类也不是机器人。那什么情况下,会产生“抖动”呢?
场景
联想一个平平无奇的搜索框,当用户输入的时候,需要实时给出搜索建议。
第一反应肯定是监听输入框的 input
事件,随着内容的变化,发出请求,拉取建议的数据。
如下面的例子(这里 input
被触发时,执行 console.log('input')
, 并且用 console.log('fetch')
代指请求):

可以看到,每一个字符的增减,都会触发请求。
这种情况,就属于“抖动”。
服务器接收到这样的请求,肯定是一脸懵啊,这谁顶得住?
这个时候,就需要像手机相机一样,做一些操作,减少抖动对网络请求的影响,减轻服务器的压力。
怎么做
“抖动”情景下,连续输入,导致发送了多次一样的请求。函数防抖的处理方式是:先规定一个时间段,比如一秒,输入内容触发 input
,一秒之后再发送请求,假如一秒内又产生了新的输入,那么重新计时,点击过后一秒再发送请求。
这样一来,规定时间段内的所有输入,只会产生一次请求。不管打字多快的手速,也战胜不了防抖的函数。
怎么写
直接上代码:
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 suggest = () => {
console.log('fetch')
}
const debounceSuggest = debounce(suggest, 500)
let btnSearch = document.getElementById('search')
btnSearch.addEventListener('input', () => {
console.log('input')
debounceSuggest()
})
这里将 suggest
函数传入 debounce
函数,并设置延迟时间为 500 毫秒。 debounce
返回的函数赋给 debounceSuggest
, 然后在提交按钮 btnSearch
的 input
事件回调中执行 debounceSuggest
.
看下效果:

上图中,一开始的几次连续输入,都不会触发 suggest
,打印 fetch
,直到整个单词 'hello'
输入完成,才触发了一次 suggest
. 接着连续删除,也只触发了一次 suggest
.
总结
函数的防抖将一定时间内的多次操作,减少为一次,去除冗余,节约资源。