首先我们可以通过一个可视化工具对节流和防抖有一个很好的认识,如下图所示当鼠标在区域中移动的时候会一直检测鼠标移动分别对应Regular普通/debounce防抖/throttle节流三种处理方式。
节流和防抖
什么是节流throttle
从上图可以看出节流设置了之后是每隔一段时间获取一次参数,如果没有设置节流函数(对比普通设置),那么在事件发生的时候,事件处理函数会不断被触发。主要实现的方式是每隔一定的时间就来执行时间处理函数。
适用于每隔一段时间就执行回调的场景主要应用:
- 滚动加载,加载更多或滚到底部监听
- 鼠标不断点击触发,例如提交表单,每隔一段时间才能触发提交,避免多次提交
- 搜索框,搜索联想功能,每隔一段时间就联想一次
什么是防抖debounce
同样从上图debounce中可以发现,虽然中间不断触发,但是只会在最后一次触发,并且等待一段时间之后处理函数才会执行。主要实现方式是利用clearTimeout
和setTimeout
当设定时间内出现再次执行的情况就清空定时器,重新开始执行,直到最后不清空就执行处理。
适用于连续的事件只需触发一次回调的场景主要应用:
- 窗口大小Resize,只需在每次窗口调整完成后,计算窗口大小
- 手机号、邮箱验证输入检测
节流和防抖的实现
就用上面的可视化工具的案例,通过监测鼠标移动来监测是否实现了节流/防抖,可以看到鼠标不断移动的时候,这个函数会被多次触发。
<script>
document.addEventListener('mousemove',(e) => {
console.log('鼠标移动')
})
</script>
实现节流
根据之前的想法,节流是在每段时间内只执行一次,可以的得到以下函数。
function throttle (fn,wait) {
// 通过定时器来实现,也可通过时间戳来实现
let timer;
return () => {
// 返回一个函数
// 如果定时器存在,就是直接返回不执行
if (timer) return;
// 如果定时器不存在就设置一个定时器
timer = setTimeout(() => {
// 执行传入的函数
fn()
// 定时器结束之后就清空定时器
timer = null;
// 等待设定好的时间
}, wait);
};
}
function move () {
console.log('鼠标节流移动'+ new Date())
}
document.addEventListener('mousemove', throttle(move,1000))
可以得到,根据设置的1s的时间间隔,这个函数才会被触发一次,而不是上面每次都触发。
节流函数
实现防抖
在执行这个函数的时候,只要在定时期间还执行了就会清空定时器并且重新开始定时,所以只会执行最后一次。
function debounce (fn,wait) {
// 设置定时器
let timer;
return () => {
// 首先要清空定时器
clearTimeout(timer)
// 如果定时器结束前没有被清空就会执行
timer = setTimeout(fn, wait)
}
}
document.addEventListener('mousemove', debounce(move2,500))
添加后防抖就能执行
防抖函数
更进一步
通过上面的两个案例可以发现不管是节流还是防抖都不是立即执行的,在现实案例中这种情况肯定会影响用户体验,如果想要立即执行的节流和防抖应该如何更改?
只要增加标示位flag,用于标示是否是第一次执行即可实现第一次就执行。
立即执行节流
function throttle_2(fn,wait){
// flag用于标示是否执行
let flag = true
let timer
return function(){
if(flag) {
// 传入的参数放在外面,一开始会直接执行
fn()
// 更改flag
flag = false;
// 设置定时器,更改flag
// 如果定时器还没有执行,那么flag就是false,就不会进入这个函数
// 只有定时器执行了,flag为true了,fn才会再次执行,达到节流的目的
timer = setTimeout(() => {
flag = true
},wait)
}
}
}
立即执行防抖
function debounce_2(fn,wait){
let timer
let flag = true;
return function(){
// 进入函数就清空定时器
clearTimeout(timer);
// flag的标志位一开始为true,进入执行
if(flag){
// 为true的时候就执行函数,并且更改标识符为false
fn()
flag = false
}
// 这个定时器设置在外面,当定时器完成定时之后才会执行
timer = setTimeout(() => {fn()},wait)
}
}