防抖(debounce)

监听浏览器滚动事件,返回当前滚条与顶部的距离

功能:

  • 很多网站会提供这么一个按钮:用于返回顶部。
  • 这个按钮只会在滚动到距离顶部一定位置之后才出现,那么我们现在抽象出这个功能需求
  • 监听浏览器滚动事件,返回当前滚条与顶部的距离
    1. function showTop () {
    2. var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
    3.   console.log('滚动条位置:' + scrollTop);
    4. }
    5. window.onscroll = showTop

    问题:
    ● 这个函数的默认执行频率,太!高!了!。 点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次!
    1. 滚动条位置:2520
    2. 滚动条位置:2898
    3. 滚动条位置:3024

解析

基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

  • 如果在200ms内没有再次触发滚动事件,那么就执行函数
  • 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时
    效果:如果短时间内大量触发同一事件,只会执行一次函数。
    实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:
  1. /*
  2. * fn [function] 需要防抖的函数
  3. * delay [number] 毫秒,防抖期限值
  4. */
  5. function debounce(fn,delay){
  6. let timer = null //借助闭包
  7. return function() {
  8. if(timer){
  9. clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
  10. timer = setTimeout(fn,delay)
  11. }else{
  12. timer = setTimeout(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
  13. }
  14. }
  15. }

写完会发现其实 time = setTimeout(fn,delay)是一定会执行的,所以可以稍微简化下:

  1. /*****************************简化后的分割线 ******************************/
  2. function debounce(fn,delay){
  3. let timer = null //借助闭包
  4. return function() {
  5. if(timer){
  6. clearTimeout(timer)
  7. }
  8. timer = setTimeout(fn,delay) // 简化写法
  9. }
  10. }
  11. // 然后是旧代码
  12. function showTop () {
  13. var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  14.   console.log('滚动条位置:' + scrollTop);
  15. }
  16. window.onscroll = debounce(showTop,1000) //

为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置
此时会发现,必须在停止滚动1秒以后,才会打印出滚动条位置。

防抖(debounce)定义

  • 对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。

参考