轻松下午茶-手撕系列:防抖debounce

332 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

轻松下午茶: UnderScore为js提供了100多个函数,今天我们来撕撕debounce,来看看debounce背后实现的原理。

debounce

在官方文档中,关于debounce函数的用法是这样的:

_.debounce(function, wait, [immediate])
返回 function 函数的防反跳版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后. 对于必须在一些输入(多是一些用户操作)停止到达之后执行的行为有帮助。 例如: 渲染一个 Markdown 格式的评论预览, 当窗口停止改变大小之后重新计算布局, 等等.

在 wait 间隔结束时,将使用最近传递给 debounced(去抖动)函数的参数调用该函数。

传参 immediate 为 true, debounce 会在 wait 时间间隔的开始调用这个函数 。(注:并且在 waite 的时间之内,不会再次调用。)在类似不小心点了提交按钮两下而提交了两次的情况下很有用。 所以在解决一些因为网络问题或者一些其他原因而导致用户多次点击按钮多次提交数据的情况下很有帮助,下面我们就用一个例子来帮助大家理解debounce的底层原理。

我们在页面引入一个点击事件,当我们点击btn按钮的时候就会执行一次request()函数,就会得到一个‘请求成功’,所以我们点击多少次就会执行多少次request()函数;而如果我们规定用户在连续多次点击按钮的情况下,即点击过于频繁,如果再像前面一样多次点击多次执行就会造成一些不好的情况,所以我们希望比如用用户两次点击事件的时间间隙超过0.5s的时间间隔来判断用户不是连续的点击按钮,如果两次点击事件的时间间隔没有超过0.5秒,我们就不执行第一次的点击事件,而是执行第二次的,从而降低因为点击过于频繁导致数据上传次数过多这种情况带来的一些风险。我们就可以使用debounce函数来解决这个问题。

3.png

快速的点击五次按钮,结果会出现五次执行:打印出五次请求成功

Snipaste_2022-08-04_18-46-34.png 而使用debounce函数,快速的点击五次按钮,只会执行最后一次,并且

4.png 2.png

那么这个debounce到底是咋实现的呢?先看看debounce函数:

_.debounce(function, wait, [immediate])

debounce函数接收function 函数以及在函数最后一次调用时刻的 wait 毫秒,当然第三个参数暂时可以不看。

整理一下思路:无非就是要让五次点击事件的前四次不执行,然后再第五次的点击后,如果传入的wait毫秒之内没有更多的点击事件,我们就再执行传入的function参数。我们要在wait毫秒之后再执行传入的第一个函数参数,第一反应是不是用setTimeout定时器来判断两次点击事件之间的时间间隙,只要在设置的wait间隙时间内只要不点击按钮,我们将前四次的点击事件清除就可以了

 //这个函数的功能就是将我们要执行的函数包装一下即:实现在一定的时间内点击多次,但是只执行最后一次的点击事件
function debounce1(fn,wait){  //传入两个参数,一个是要包装的函数,一个是判断两次点击的事时间间隙wait
    var timeout   //申请一个变量来存储定时器
    return function(){  //将传入的函数执行完毕返回出来
        clearTimeout(timeout)  //清除上一个定时器
        timeout=setTimeout(()=>{  //如果点击了按钮,就重新创建了一个新的定时器
            fn()
        },wait)
    }
}
因为每次点击按钮,就会重新创建一个定时器,如果用户点击过快,就会导致上一次点击事件的结果还没有出来我们只要在第二次点击的时候,把上一次的定时器清除就可以重新计时判断用户有没有点击

但是上面的写法会改变参数fn中this以及js中事件执行的回调函数(js中提供的事件执行的回调函数里面一定会有一个事件参数)里面事件参数event的指向

    因为使用debounce方法,原本这个request里面会有事件参数的,
    但是现在因为使用了deboumnce方法点击事件里面的回调函数是lazyLayout,所以现在这个事件参数是在lazyLayout里面
     btn.addEventListener('click',request)
     btn.addEventListener('click',lazyLayout)     
     

所以优化一下:

用显示绑定把返回出来的匿名函数中的this绑定到fn中的this;将event传给fn
function debounce1(fn,wait){
    var timeout
    return function(){
        const args=arguments//将匿名函数的参数拿到传给fn,也就是交给了request

        const context=this
        clearTimeout(timeout)
        timeout=setTimeout(()=>{
            fn.apply(context,args)
            // console.log(event);//打印出事件参数数组
        },wait)
    }

}