前面在实现 Vue 里面的滚动加载时,我们监听页面的滚动事件,然后不断获取元素距离页面顶部的距离。
代码如下:
1 | window.onscroll = () => { |
但是在执行过程中,当我们轻轻滚动一下,浏览器会打印出很多 top 的值,说明函数执行的频率相当高。
但是我们不希望页面的滚动事件频繁的执行,毕竟浏览器的性能是有限的,所以对于这种情况就需要我们进行优化。
防抖(debounce)
基于以上需求,首先提出一种思路:在第一次触发事件的时候,不立即执行函数,而是等待一个时间期限 delay然后:
- 如果在这个时间 delay 内没有再次触发该事件,那么久执行函数
- 如果在这个事件 delay 内再次触发该事件,那么当前的计时器取消,重新开始计时
实现效果:短时间内大量触发同一事件,只执行一次函数
实现:根据以上分析,我们肯定需要使用 setTimeout 这个函数,同时还需要保存计时器,以方便后续取消计时。那么函数的实现方法如下:
1 | /* |
然后可以配合之前的代码进行使用:
1 | window.onscroll = debounce(showTop, 1000) |
此时运行代码并滚动,会发现必须停止滚动 1000ms以后,才会打印出 top 的值。
这样我们就是先了防抖函数,现在给出防抖的定义:
短时间内连续触发相同的事件,防抖就是让在某个时间期限内事件处理函数只执行一次
节流(throttle)
思考上面方案就可以发现一个问题:在限定时间内不断触发事件,只要不停止触发,理论上就永远不会执行函数输出结果。
但是如果我们希望:即使用户不断触发事件,也能在某个时间间隔之后给出反馈呢。
其实这就类似于定时开放的函数,也就是让函数执行一次后,在某个时间段内暂时失去效果,即使触发事件,函数也不会执行,过了这段时间再重新激活(类似于技能冷却)。
实现效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
实现:这里可以借助 setTimeout 来实现,并加上一个状态位 valid 表示函数是否可执行。
1 | function throttle(fn ,delay) { |
需要注意的是,节流函数并不止上面这种方案,也可以直接将 setTimeout 返回的标记当作条件判断当前定时器是否存在,如果存在表示还在冷却,并且在执行 fn 之后消除定时器表示激活。
应用场景举例
- 搜索框 input 事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值,就当作用户输入完成,然后开始搜索
- 页面 resize 事件,常用于需要做页面适配的时候。需要根据最终呈现的页面情况进行 DOM 渲染,这种情况一般用防抖,因为只需要判断最后一次的变化情况
- 页面滚动实现懒加载,可以使用防抖,因此在页面滚动过程中最终都会无法滚动,从而执行函数。