一篇文章带你彻底认识「防抖和节流」

在日常开发中,我们经常会遇到这样的问题:用户在表单页面多次点击提交按钮,或者在搜索框快速输入时频繁触发请求,带来了高并发。这些高频操作不仅影响用户体验,还可能给服务器带来巨大压力。如何解决这些问题呢?

为什么需要防抖和节流?

比如上文提到的场景:用户填写完表单后点击提交按钮,由于网络延迟,页面没有及时响应。用户以为没点上,于是连续点击了好几次,结果服务器收到了多条重复请求,压力倍增。

又比如,用户在搜索框输入内容时,每输入一个字母就发起一次请求,短短几秒钟就可能产生几十上百次请求,既浪费资源又影响性能。

这些问题的本质是高频率的事件触发,而防抖和节流就是为了解决这种高频操作的。

什么是防抖和节流?

防抖(Debounce)

定义: 防抖指的是在一段时间内,如果事件被频繁触发,只在最后一次触发后才执行操作。
通俗理解: 你停下来我才干活。

防抖的实现步骤如图:

下面让我们以点击按钮为例,来手写一个防抖函数,来实现防抖效果:

  1. 首先在 body 标签里面创建一个提交按钮,然后script钟获取按钮,给按钮绑定一个监听事件,这样我们就可以点击一下按钮,发送一次请求了
js 复制代码
<body>
    <button id="btn">提交</button> //创建一个提交按钮
</body>

<script>
    function send(){
        console.log('已点击按钮!');
    }
    const btn = document.getElementById("btn");
    btn.addEventListener("click",send)  //给btn按钮绑定点击监听事件
</script>
  1. 现在我们来写一个防抖函数来限制用户一直点击按钮频繁发送请求的问题
  • 防抖函数 debounce 接收两个参数:点击事件函数 send 和等待时间 wait
  • debounce 的外部作用域中定义了一个 timer 变量,用于保存定时器的 ID。
  • debounce 返回一个新的函数,这个函数每次被触发时,都会先判断 timer 是否有值,如果有,则清除上一次的定时器,然后重新设置一个新的定时器。
  • 由于闭包的存在,timer 变量始终被返回的函数所引用,每次点击都能访问和修改同一个 timer,从而实现防抖的效果。
  • 只有在最后一次点击后的 wait 毫秒,send 函数才会被真正执行,避免了用户频繁点击时多次触发事件。
js 复制代码
<script>
    function send(){
        console.log('已点击按钮');
    }
    const btn = document.getElementById("btn");
    btn.addEventListener("click",debounce(send,1000))
    
    function debounce(send,wait){
        let timer;
        return function(){
            if(timer) clearTimeout(timer) //timer有值,说明上次定时器还没执行完,这时清除上次定时器
            timer = setTimeout(function(){
            send()
        }, wait);
    }
}
</script>

现在不管我们点击多少次按钮,在我们不点击后1秒,最后只会打印一个结果了。 现在基本的防抖函数已经完成了,但是要注意 this 指向问题,按理说按钮执行了监听事件后 this 应该要指向 btn 的,但是却指向了 window,这时我们在 send 函数中打印 this 看看:

3. 这时候我们还要考虑 this 指向问题和参数问题

js 复制代码
<script>
let btn = document.getElementById('btn')

    function send(e) {
        console.log('已点击按钮',this)
    }

    btn.addEventListener('click', debounce(send, 1000))

    function debounce(send, wait) {
        let timer = null
        return function(...arg) {
            let that = this
            clearTimeout(timer)
            timer = setTimeout(function() {
                send.call(this,...arg)
            }, wait)
        }
    }
</script>
  • 为了保证事件处理函数中的 this 指向正确,需要在防抖函数返回的闭包中用变量 that 保存当前 this,并在 setTimeout 的回调中用 fn.call(that, ...arg) 调用原函数。

  • 为了保证事件对象等参数能正确传递,使用 ...arg 收集参数,并原样传递给 fn。

  • 这样,防抖函数不仅能防止高频触发,还能保证 this 和参数的正确性,适用于大多数事件处理场景。


节流(Throttle)

定义: 节流指的是在一定时间内,只允许事件触发一次。
通俗理解: 定时干活,超时无效。

节流的实现步骤如图:

节流函数实现 步骤和防抖差不多,就不一一写出来了,直接一步到位

js 复制代码
    <script>
        let bit = document.getElementById('btn')

        function send() {
            console.log('像后端发请求');
            
        }
        btn.addEventListener('click', throttle(send, 2000))

        function throttle(send, wait) {
            let preTime = null
            return function(...arg) {
                let nowTime = Date.now() // 第一次点击的时间戳
                if(nowTime - preTime >= wait){
                    send.call(this, ...arg)
                    preTime = nowTime
                }
            }
        }
    </script>
  • 每次触发事件时,判断距离上次执行的时间是否超过 wait,如果超过就执行,否则忽略。
  • 在一段时间内,无论触发多少次,只有第一次会执行,后面会等到时间间隔到了才会再次执行。

防抖和节流的区别

防抖(debounce) 节流(throttle)
执行时机 最后一次触发后 每隔一段时间执行一次
典型场景 输入框、按钮防重复提交 滚动、拖拽、频繁点击
实现方式 定时器+清除上次定时器 时间戳/定时器

总结:

  • 防抖:只认最后一次操作,适合输入、提交等场景。
  • 节流:有节制地执行,适合滚动、点击等场景。
相关推荐
超人不会飛1 分钟前
就着HTTP聊聊SSE的前世今生
前端·javascript·http
蓝胖子的多啦A梦4 分钟前
Vue+element 日期时间组件选择器精确到分钟,禁止选秒的配置
前端·javascript·vue.js·elementui·时间选选择器·样式修改
夏天想6 分钟前
vue2+elementui使用compressorjs压缩上传的图片
前端·javascript·elementui
The_cute_cat8 分钟前
JavaScript的初步学习
开发语言·javascript·学习
海天胜景11 分钟前
vue3 el-table 列增加 自定义排序逻辑
javascript·vue.js·elementui
今晚打老虎z15 分钟前
dotnet-env: .NET 开发者的环境变量加载工具
前端·chrome·.net
用户38022585982420 分钟前
vue3源码解析:diff算法之patchChildren函数分析
前端·vue.js
烛阴26 分钟前
XPath 进阶:掌握高级选择器与路径表达式
前端·javascript
小鱼小鱼干29 分钟前
【JS/Vue3】关于Vue引用透传
前端
JavaDog程序狗31 分钟前
【前端】HTML+JS 实现超燃小球分裂全过程
前端