【知识梳理】防抖(debounce)和节流(throttle)

5,548 阅读4分钟

前言


 知识只有配合实践,吸收内化成自己的东西,才能在用到的时候举重若轻,挥洒自如。

自我提问


  1. 防抖和节流的使用场景是什么?使用这两个东西的目的是什么?
  2. 防抖和节流是什么意思?为什么叫这个名字?有什么区别?

使用场景


 技术的诞生都是为了解决实际生活中遇到的问题的,互联网的诞生解决了空间距离造成的交流的阻碍。弄明白了某个知识诞生的背景,那么更能够领会这个知识点的内涵。

 在前端开发中,我们经常会遇到一些浏览器事件触发频率很高的场景。

  1. 场景一:移动端网页中,经常在右下角会有一个返回顶部的按钮,并且这个按钮通常都是在页面向下滚动一定距离后才会出现的。那么如何知道页面已经向下滚动了多少距离呢?需要监听页面的"scroll"事件。代码如下,
window.addEventListener('scroll', () =>{
    const scrollTop = document.body,scrollTop || document.documentElement.scrollTop;
    console.log('页面滚动距离', scrollTop);
}, false)

 在实际运行上面的代码之后,我们会发现一个问题,那就是scroll事件触发的频率太高了。使用鼠标滚轮稍微滚动一下,事件就会触发4、5次。这对于我们业务逻辑没有太大好处,反而损害了浏览器性能。

返回顶部按钮 2. 场景二:输入框验证输入字符是一个很常见的需求,如果你的验证逻辑需要调用后端接口的话,那就比较耗费时间了。例如搜索框输入,每输入,都要调用API查询关联的词条下拉展示。这种情况下,我们不希望每次输入都要掉接口(因为有的时候输入的是拼音),而是在输入完全后再调用接口。

 总结一下,上面提到的应用场景。都是事件触发频率过高,损害了浏览器性能。作为开发者,希望能够通过某种方式控制事件触发的评论,并且对业务逻辑没有影响。而这也是防抖和节流的目的———控制事件触发的频率。

防抖(debounce)


 上面已经说了我们希望找到一个方法控制事件触发的频率,不同的开发者有着不同的解决问题的思路,其中两个经受实际检验得到大家认可的思路就是——防抖和节流。

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

  • 如果在200ms内,事件再次触发,当前的计时取消,重新开始计时
  • 如果在200ms内,事件没有触发,那么执行事件的处理函数

效果:指定时间内连续触发事件,只在最后一次事件触发结束后的指定时间之后,执行一次处理函数。

代码实现:

/* 防抖 */
function debounce(fn, delay) {
    let timer;

    return function() {
        if (timer) clearTimeout(timer);
        
        timer = setTimeout(fn, delay);
    }
}

window.addEventListener('scroll', debounce(() => {
    let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
    console.log('页面滚动距离', scrollTop);
},1000), false);

 实际运行,可以发现只有在停止滚动1s后,才会执行处理函数。

节流(throttle)


 在防抖的基础上继续思考,如果使用防抖的解决方案,那么会出现一个新问题:如果某个用户闲着无聊,一直不停的滚动页面,那么理论上是不会执行滚动事件的处理函数的。因为只有停止滚动1s后,才会执行处理函数。这样的结果是产品不想要的,产品希望即使用户不停的滚动页面,也能隔一段时间就能执行处理函数。类似技能冷却时间,冷却时间内不能使用。

 那么我们可以给出思路:当事件第一次触发的时候,也是不立即执行函数,而是给出一个期限值比如200ms,开始计时,然后

  • 在200ms内,再次触发的事件被全部忽略,计时结束后,执行一次函数,并且清理计时器。
  • 200ms结束后,重新开始上述循环

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

代码实现:

function throttle(fn, delay) {
    let timer;
    
    return function() {
        if (!timer) {
            timer = setTimeout(() => {
                fn();
                clearTimeout(timer);
                timer = null;
            }, delay)
        }
    }
}

window.addEventListener('scroll', throttle(() => {
    let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
    console.log('页面滚动距离', scrollTop);
},1000), false);

总结:


 防抖和节流是两种解决短时间事件触发频率过高问题的思路,防抖是动态的,只有当最后一次事件触发结束之后,才会执行处理函数。中间没有处理过函数,防止了页面"抖动",可以认为保证了过程的连续平滑。而节流是离散的,明确对时间进行了分割,在指定的时间块中只执行一次事件处理函数。