带你轻松搞懂防抖和节流,一起手写实现原理

1. 防抖(Debounce)

防抖是指在某个事件频繁触发时,延迟一定时间后再执行回调函数。如果在延迟时间内又触发了同样的事件,那么会重新计时。从而避免了频繁触发回调函数,只在事件停止触发后执行一次。

就拿王者荣耀游戏来说,防抖就好比游戏里面的回城,如果在回城时间内再次触发事件(回城被打断),则会重新计算回城时间。

防抖实现步骤图如下:

应用场景:

  1. 输入框搜索建议:当用户输入时,防抖可以用于延迟发送请求,避免频繁请求后端接口。
  2. 窗口大小调整:当窗口大小调整时,防抖可以用于调整事件触发的频率,避免频繁操作导致页面抖动。
  3. 按钮防重复点击:当用户频繁点击按钮时,防抖可以用于限制按钮点击的频率,避免重复提交操作。

下面我拿上面第三点防抖的应用场景,来和大家一起一步一步手写防抖效果的代码:

首先我们先在body里面创建一个提交按钮,并且给它绑定点击监听事件,假设我们点击按钮后要执行send函数,让其打印我们好棒鸭!,那我们的初步代码就是下面这样啦。

html 复制代码
<body>
    <button id="btn">提交</button> //创建一个提交按钮
</body>
</html>
<script>
    function send(){
        console.log('我们好棒鸭!');
    }
    const btn = document.getElementById("btn");
    btn.addEventListener("click",send) //给btn按钮绑定点击监听事件
</script>

好,那我们点击按钮15次之后,就得到了下面的结果:打印了15次 那我们一直点它就一直打印,要是是这样发送请求的话,服务器不得被干冒烟,所以这时候我们就要用防抖来限制按钮点击的频率,防止用户手抖重复提交操作。接下来,我们继续理思路,单纯写send函数名是肯定行不通的,每次点击会一直调用,所以这时候我们就要用到闭包的知识了,我们在按钮监听事件里面写个debounce(send)函数的调用,将send函数名作为debounce函数实参,在里面调用,然后因为要限制按钮点击的频率,所以我们还需要设置一定的延迟时间,只有在这段时间用户不再点击按钮了,我们才执行需要的事件,这里就是执行send()方法,所以我们再给debounce(send,1000)函数加个1秒的延迟时间作为实参,这时我们再写一下代码。

html 复制代码
<script>
    function send(){
        console.log('我们好棒鸭!');
    }
    const btn = document.getElementById("btn");
    btn.addEventListener("click",debounce(send,1000))

    function debounce(send,time){
        return function(){
            setTimeout(function(){
            send()
        }, time);
    }
}
</script>

那我们的代码就写成这样啦,当页面运行时,会立即执行debounce(send,1000),然后会返回一个函数给监听事件,每次按钮被点击便会执行这个debounce函数返回的函数。这时候我们运行代码点击按钮后,便会在1秒后打印结果,不过多次点击也还是会创建多个定时器最后打印多个结果。

所以,这时候在debounce函数声明一个name变量,用来记录定时器的名字,然后在返回函数中,我们将每次点击创建的定时器赋值给name,所以我们只要在这之前判断name是否有值,如果name有值,说明上一次点击事件创建的定时器还没有执行完,所以这时我们只需把上次的定时器清空,则不会再执行上次的点击事件了,直到在延时时间内不再点击,则才会执行我们需要执行的事件。这时代码如下:

html 复制代码
<script>
    function send(){
        console.log('我们好棒鸭!');
    }
    const btn = document.getElementById("btn");
    btn.addEventListener("click",debounce(send,1000))
    
    function debounce(send,time){
        let name;
        return function(){
            if(name) clearTimeout(name) //name有值,说明上次定时器还没执行完,这时清除上次定时器
            name = setTimeout(function(){
            send()
        }, time);
    }
}
</script>

这时候我们运行后会发现,不管我们点击多少次按钮,在我们不点击后1秒,最后只会打印一个结果了。好!这时候低配版的的防抖我们就已经实现啦~哈哈哈,这还是个低配的?为什么这么说呢,我们再来看看this的指向问题,按理说按钮执行了监听事件this应该要指向btn的,这时我们在send函数中打印this看看: 结果是指向window。很显然我们现在这样写影响了send函数中this的指向问题,所以我们还得用call方法,将this指向btn,同时也还有一个问题,就是如果send函数要接收多个参数,那返回函数里调用send()也得传多个参数,是不是就太麻烦啦,所以这时候还要用到arguments类数组,它包含传递给函数的每个参数,将arguments里的每个参数展开到send函数中作为实参即可。这时候代码如下:

js 复制代码
<script>
    function send(){
        console.log('我们好棒鸭!',this);
    }
    const btn = document.getElementById("btn");
    btn.addEventListener("click",debounce(send,1000))
    
    function debounce(send,time){
        let name;
        return function(){
            let _this = this;//这个this指向btn
            let arr = arguments //包含传递给函数的每个参数
            if(name) clearTimeout(name) //name有值,说明上次定时器还没执行完,这时清除上次定时器
            name = setTimeout(function(){
            send.call(_this,...arr) //将send函数的this指向_this(也就是btn)
                                    //arguments里的每个参数展开到send函数中作为实参
        }, time);
    }
    //也可以使用箭头函数这样写
    // function debounce(send,time){
    //     let name;
    //     return function(){
    //         let arr = arguments //包含传递给函数的每个参数
    //         if(name) clearTimeout(name) //name有值,说明上次定时器还没执行完,这时清除上次定时器
    //         name = setTimeout(()=>{  //这里使用箭头函数,因为箭头函数没有this,所以里面的this依旧指向btn
    //         send.call(this,...arr) //将send函数的this指向btn(此时this指向btn)
    //                                 //arguments里的每个参数展开到send函数中作为实参
    //     }, time);
    // }
}
</script>

到这里我们的防抖代码就成功实现啦!这里面每一条代码都运用的非常巧妙,需要我们对闭包,this指向,以及第二种方法箭头函数的知识都要了解的非常清楚,知道了这些,是不是觉得手写防抖代码很简单啦~

2. 节流(Throttle)

节流是指在某个事件持续触发时,限制一定时间间隔只执行一次回调函数。比如,设置每1000毫秒最多执行一次回调函数,不论事件触发的频率有多高。

同样拿王者荣耀游戏来说,节流就好比游戏里面英雄的普通攻击,在限制一定时间点击多次,也只能攻击一次。

节流实现步骤图如下: 应用场景:

  1. 页面滚动加载:当用户滚动页面时,节流可以用于限制加载事件的触发频率,避免过多的加载请求。
  2. 频繁点击按钮:当用户频繁点击按钮时,节流可以用于限制按钮点击的频率,避免过于频繁的操作。

同样,我们通过上面的应用场景2,来和大家一起写节流的示例代码:

这里和防抖代码一样,初始代码如下:

javascript 复制代码
<body>
    <button id="btn">提交</button>
</body>
<script>
    function send(){
        console.log('我们好棒鸭!');
    }
    const btn = document.getElementById("btn");
    btn.addEventListener("click",throttle(send,1000))

    function throttle(send,time){
        return function(){
        send()
        }
}
</script> 

这里我们设置每1000毫秒最多执行一次回调函数,不论事件触发的频率有多高。所以这时候我们需要在每次调用send()函数前判断上次点击的时间和下次点击的时间差是否大于延时时间,如果大于则执行send函数,否则就一直等待到下次点击时间与上次点击时间差大于延时时间。在这里,这样就保证了每次在1000毫秒里,最多只能触发一次send函数。下面我们看看代码:

html 复制代码
<script>  
    function send(){  
        console.log('我们好棒鸭!');  
    }  
    const btn = document.getElementById("btn");  
    btn.addEventListener("click",throttle(send,1000))  
  
    function throttle(send,delay){  
 //pretime记录的是上次点击的时间(初始值为页面运行时间)
        let pretime = Date.now() 
        return function(){  
//前判断上次点击的时间和下次点击的时间差是否大于延时时间
        if(Date.now() - pretime > delay){  
        send()  
        pretime = Date.now()  
        }  
    }  
}  
</script>

这里面pretime记录的是上次点击的时间(初始值为页面运行时间),然后Date.now是我们点击的时间,只有它们相差1s才会执行send函数,同时更新prevtime为当前点击时间(相当于下次点击前的上次点击时间),然后下次再次点击又会有一个点击时刻的时间,若这个时间和prevtime差不超过1s,则不执行结果,一直到下次点击时间与prevtime差大于1s,才执行send函数。所以这样就非常优雅的保证了每次1s内无论我们点击多少次都只会执行一次send函数。

同样,和防抖一样,我们还要解决send函数this的指向问题,可以用call方法让它指向btn,还要用到arguments类数组,它包含传递给函数的每个参数,将arguments里的每个参数展开到send函数中作为实参。这样我们的节流代码也就成功实现啦~看看最终代码:

html 复制代码
<script>
    function send(){
        console.log('我们好棒鸭!',this);
    }
    const btn = document.getElementById("btn");
    btn.addEventListener("click",throttle(send,1000))

    function throttle(send,delay){
        let pretime = Date.now()
        return function(){
        let arr = arguments //包含传递给函数的每个参数
        if(Date.now() - pretime > delay){//
        send.call(this,...arr)//此时this就是指向btn
        pretime = Date.now()
        }
    }
}
</script>

喔哦,非常棒呢!大家是不是觉得节流代码也非常简单呢~节流代码也是每一条代码都非常有用呢,要是都可以搞明白,说明大家对闭包和this指向问题都非常了解啦~

总结

防抖:在规定时间内,多次触发只响应最后一次。(多次触发,只执行最后一次)

节流:在规定时间内,多次触发只响应第一次。(规定时间内,只触发一次)

以上代码分别展示了防抖和节流的实现方式。我们可以根据实际需要选择防抖还是节流来优化事件触发的频率,提升用户体验。

如果觉得文章对您有所帮助的话,麻烦给小博主点点关注,点点赞咯😘,有问题欢迎各位小伙伴评论喔😊~

相关推荐
掘金安东尼8 分钟前
用 WebGL + Solid.js 构建混合材质 Shader
前端·webgl
恋猫de小郭12 分钟前
Flutter 小技巧之有趣的 UI 骨架屏框架 skeletonizer
android·前端·flutter
江城开朗的豌豆13 分钟前
玩转React Hooks
前端·javascript·react.js
阿酷tony17 分钟前
教育场景下禁用html5播放器拖动进度条的例子
前端·html·html5·在线教育场景·禁止播放器拖动
前端小巷子38 分钟前
Vue3 响应式革命
前端·vue.js·面试
一狐九1 小时前
Flutter如何通过GlobalKey调用组件内的方法
前端·flutter
wyzqhhhh1 小时前
前端如何处理首屏优化问题
前端
杨荧1 小时前
基于Python的反诈知识科普平台 Python+Django+Vue.js
大数据·前端·vue.js·python·数据分析
22jimmy2 小时前
JavaWeb(二)CSS
java·开发语言·前端·css·入门·基础
m0_738120725 小时前
CTFshow系列——命令执行web38-40
前端·windows·安全·web安全