0%

防抖和节流

前面在实现 Vue 里面的滚动加载时,我们监听页面的滚动事件,然后不断获取元素距离页面顶部的距离。

代码如下:

1
2
3
4
5
6
window.onscroll = () => {
const el = document.querySelector(".el")
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
console.log('top', top)
}

但是在执行过程中,当我们轻轻滚动一下,浏览器会打印出很多 top 的值,说明函数执行的频率相当高。

但是我们不希望页面的滚动事件频繁的执行,毕竟浏览器的性能是有限的,所以对于这种情况就需要我们进行优化。

防抖(debounce)

基于以上需求,首先提出一种思路:在第一次触发事件的时候,不立即执行函数,而是等待一个时间期限 delay然后:

  • 如果在这个时间 delay 内没有再次触发该事件,那么久执行函数
  • 如果在这个事件 delay 内再次触发该事件,那么当前的计时器取消,重新开始计时

实现效果:短时间内大量触发同一事件,只执行一次函数

实现:根据以上分析,我们肯定需要使用 setTimeout 这个函数,同时还需要保存计时器,以方便后续取消计时。那么函数的实现方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
fn 为需要防抖的函数
delay 为时间期限
*/
function debounce(fn, delay){
// 初始化计时器
let timer = null
return function(){
if(timer) {
// 如果正在进行一个计时过程,说明在 delay 事件内重复触发该事件,所以取消当前的计时
clearTimeout(timer)
}
// 新建一个计时器
timer = setTimeout(fn, delay)
}
}

然后可以配合之前的代码进行使用:

1
2
3
4
5
6
window.onscroll = debounce(showTop, 1000)
function showTop(el){
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
console.log('top', top)
}

此时运行代码并滚动,会发现必须停止滚动 1000ms以后,才会打印出 top 的值。

这样我们就是先了防抖函数,现在给出防抖的定义:

短时间内连续触发相同的事件,防抖就是让在某个时间期限内事件处理函数只执行一次

节流(throttle)

思考上面方案就可以发现一个问题:在限定时间内不断触发事件,只要不停止触发,理论上就永远不会执行函数输出结果。

但是如果我们希望:即使用户不断触发事件,也能在某个时间间隔之后给出反馈呢

其实这就类似于定时开放的函数,也就是让函数执行一次后,在某个时间段内暂时失去效果,即使触发事件,函数也不会执行,过了这段时间再重新激活(类似于技能冷却)。

实现效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。

实现:这里可以借助 setTimeout 来实现,并加上一个状态位 valid 表示函数是否可执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function throttle(fn ,delay) {
// 初始化 valid = true 表示函数可执行
let valid = true
return function() {
if(!valid) {
// 如果函数不可执行,直接 return
return false
}
// 如果函数可执行, 放入任务队列等待执行
setTimeout(() => {
fn()
// 函数执行完成,valid 设为 true 表示函数可执行
valid = true
}, delay)
// valid 设为 false,表示正在等待执行该函数,函数暂时不可用
valid = false
}
}

需要注意的是,节流函数并不止上面这种方案,也可以直接将 setTimeout 返回的标记当作条件判断当前定时器是否存在,如果存在表示还在冷却,并且在执行 fn 之后消除定时器表示激活。

应用场景举例

  1. 搜索框 input 事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值,就当作用户输入完成,然后开始搜索
  2. 页面 resize 事件,常用于需要做页面适配的时候。需要根据最终呈现的页面情况进行 DOM 渲染,这种情况一般用防抖,因为只需要判断最后一次的变化情况
  3. 页面滚动实现懒加载,可以使用防抖,因此在页面滚动过程中最终都会无法滚动,从而执行函数。