浅谈JS防抖节流

962 阅读3分钟

防抖和节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。所以还是很有必要早点掌握的。

在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。

话不多说,先看一个demo

动画.gif

当你监听一个点击事件,在控制台打印提交成功,如果不做任何操作,就会每点一次打印一次,然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。

1.png

防抖

基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如1s然后: 如果在1s内没有再次触发点击事件,那么就执行函数 如果在1s内再次触发滚动事件,那么当前的计时取消,重新开始计时

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

动画2.gif

下面看一下代码

 let btn = document.getElementById('btn')
 btn.addEventListener('click', debounce(a, 1000)) 
 function a(b) {
     console.log('提交成功');
 }
 function debounce(fn, delay) {
     let timer = null
     return function () {
         let arg = arguments
         clearTimeout(timer)
         timer = setTimeout(() => {
             fn.apply(this.arg)
         }, delay)
     }
 }

节流

继续思考,使用上面的防抖方案来处理问题的结果是: 如果在限定时间段内,不断触发点击事件(比如某个用户闲着无聊,一直点),只要不停止触发,理论上就永远打印,但是如果产品同学的期望处理方案是:即使用户不断点击按钮,也能在某个时间间隔之后给出反馈呢?

其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。

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

实现:这里借助new Date()获取时间来做一个简单的实现,判断两次执行的时间差,大于或者等于就执行。

动画3.gif

同样也看一下代码

let btn = document.getElementById('btn')
btn.addEventListener('click', throttle(a, 2000))
function a() {
    console.log('提交成功');
}
var throttle = function (fn, delay = 1000) {
    let prev = 0;
    return function () {
        let arg = arguments;
        let now = Date.now()
        if (now - prev >= delay) {
            fn.apply(this, arg)
            prev = Date.now()
        }
    }
}

总结

函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。

区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。