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

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

为什么需要防抖和节流?

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

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

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

什么是防抖和节流?

防抖(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 小时前
从零开发一个 Coding Agent(七):实现纯文本 Agent Loop
前端·人工智能
大家的林语冰1 小时前
👉 尤雨溪再次成立新公司,同时官宣 Pinia 4 正式发布!
前端·javascript·vue.js
浮生望1 小时前
React useRef 深度解析:从DOM操作到Web Worker多线程协作
前端
浮生望1 小时前
React Router v7 实战全景:路由配置、嵌套路由、鉴权守卫与懒加载完整指南
前端
用户938515635071 小时前
从零在浏览器里跑 DeepSeek-R1:WebGPU + Transformer.js 全链路实战(二)
前端·javascript·typescript
雪隐2 小时前
个人电脑玩AI-16让5060 Ti给你打工——MiniMax H3 提速实录:从"泡杯茶等视频"到"视频等你",一张 5060 Ti 的自我修养
前端·人工智能·后端
FungLeo2 小时前
Flutter Web 中文字体困境与渲染器选择:CanvasKit vs HTML renderer 实战
前端·flutter·html
BD_Marathon2 小时前
部署Spark
大数据·javascript·spark
敲代码的玉米C2 小时前
我修的那个 bug,制造了另一个 bug
前端·人工智能·架构
gis开发之家2 小时前
《Vue3 从入门到大神50篇》Vue3 源码详解(二十):生命周期钩子源码解析 —— onMounted / onUpdated 如何实现?
前端·javascript·前端框架·vue3·vue3源码