防抖与节流:前端性能优化的双子星

40 阅读6分钟

防抖与节流:前端性能优化的利器

在现代 Web 应用中,用户交互日益复杂,键盘输入、滚动、窗口缩放等事件往往以极高的频率被触发。若不对这些高频操作加以控制,轻则造成大量无效计算和网络请求,重则引发页面卡顿甚至崩溃。
例如,在搜索框中每按一次键就发起一次 AJAX 请求,不仅浪费带宽,还可能因响应顺序错乱而展示错误结果。

为解决这类性能瓶颈,防抖(Debounce)节流(Throttle) 应运而生——它们通过限制函数执行频率,在保障用户体验的同时,显著提升应用的响应效率与稳定性。这两种看似简单却极其有效的技术,已成为前端性能优化的基石。

一、问题引入:高频事件带来的性能瓶颈

想象这样一个场景:用户在一个搜索框中输入关键词,每输入一个字符,就立即向服务器发送一次请求,获取相关的搜索建议。如果用户快速连续输入“javascript”,那么系统将依次发出 10 次请求(j → ja → jav → … → javascript)。这不仅造成大量无效的网络开销,还可能导致响应顺序错乱(后发出的请求先返回),最终展示错误的建议结果。

用这段代码来模拟一下请求的过程,你就能感受到它是多么的糟糕:

 <input type="text" id="undebounce"/>
 <script>
  function ajax(content){
            console.log('ajax request',content);
        }
         const inputa =document.getElementById('undebounce')
        // 频繁触发
        //模拟请求
        inputa.addEventListener('keyup',function(e){
            ajax(e.target.value)
        })
        
 </script>

image.png 可以看到,仅仅只是一个搜索简单的内容,就发起了N次请求,如果不加以控制,有可能就会导致浏览器的崩溃

类似的问题也出现在页面滚动加载、窗口大小调整、按钮重复点击等场景中。因此,我们需要一种机制,来限制函数的执行频率,从而在保证用户体验的同时,提升系统性能。


二、防抖(Debounce):只在最后一次触发后执行

原理

防抖的核心思想是:在事件被频繁触发时,只在最后一次触发后的一定延迟时间内执行一次回调函数。如果在延迟期间再次触发事件,则重新计时。

这种策略特别适用于那些只需要关心最终状态的场景。

实现思路(模拟)

通过 setTimeout 和闭包,我们可以轻松实现一个防抖函数:

 <input type="text" id="debounce"/>
 <script>
function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}
</script>

因为js闭包机制,所以我们在外层定义一个计时器的ID,所有的计时器都只能经过这个变量来进行记录。 每次调用返回的函数时,都会清除之前的定时器并重新设置。只有当一段时间内没有新的触发,才会真正执行原始函数。

image.png 可以看到,把控好触发的时间就能正确的处理用户的输入,并且优化性能

防抖的典型应用场景:搜索建议

以百度搜索为例,当用户在搜索框中输入内容时,我们并不需要对每一个字符都发起请求。使用防抖后,只有当用户停止输入(比如停顿 300ms)时,才向服务器请求搜索建议。这样既减少了不必要的请求,又避免了响应混乱,提升了整体体验。

image.png 这样的设计不仅优化了用户的体验,让用户能够享受到搜索建议带来的便捷,同时也降低了性能问题带来的开销。


三、节流(Throttle):固定时间间隔内最多执行一次

原理

与防抖不同,节流的策略是:无论事件触发多么频繁,保证在指定的时间间隔内最多只执行一次函数。它像是给函数执行加上了一个“射速限制”——即使你疯狂点击鼠标,在 FPS 游戏中子弹也不会无限快地射出。

实现思路(模拟)

节流可以通过记录上次执行时间或使用 setInterval 来实现。这里采用时间戳方式:

 function throttle(fn,delay){
            let last,deferTimer;
            return function(...args){
                let that = this;//this 丢失问题
                let _args = args;
                let now =+ new Date();// 类型转换,转换成为毫秒数
                if(last&&now-last<delay){
                    clearTimeout(deferTimer);
                    // 还没到时间,但是是最后一次触发,必须执行
                    deferTimer =setTimeout(function(){
                        last = now;
                       
                        fn.apply(that,_args)
                    },delay)
                }else{
                    last = now;
                    fn.apply(that,_args)
                }
            }
        }

last代表上一次请求的时间,defertiem用于处理频繁请求的边界情况。
该函数确保两次执行之间至少间隔 delay 毫秒。
且如果两次请求之间的时间过短就会被判断为频繁请求,但是如果这是用户最后一次请求,那么也必须执行,所以在清除之后也需要进行一次请求的处理。

不管用户的输入多么频繁,节流函数都限制着它的请求,只以固定的速度发起每一次请求,这样不仅优化了用户体验,同样也降低了性能的开销 屏幕录制 2025-12-30 222241.gif

节流的典型应用场景:滚动加载

以京东商品列表页为例,当用户快速向下滚动页面时,若每次滚动都触发“加载更多”逻辑,会导致大量重复请求和渲染压力。然后防抖却不适用于这里,如果使用防抖进行限制,那么只有在用户松开滚轮时才执行,这显然不是一个好的用户体验。通过节流,我们可以设定每 500ms 最多检查一次是否需要加载新数据。这样既能及时响应用户滚动行为,又避免了性能浪费。

image.png 其他常见场景还包括:

  • 窗口 resize 事件处理
  • 鼠标移动追踪(如拖拽)
  • 按钮防重复提交(配合 loading 状态)

四、防抖 vs 节流:如何选择?

特性防抖(Debounce)节流(Throttle)
执行时机事件停止触发后延迟执行固定时间间隔内最多执行一次
适用场景搜索建议、表单验证、窗口 resize 结束滚动加载、鼠标移动、FPS 射击逻辑
是否保证执行如果持续触发,可能一直不执行即使持续触发,也会定期执行

简单记忆:

  • 防抖:等你“消停”了我才干活。
  • 节流:我可以一直干,但得按节奏来。

结语

防抖与节流虽实现原理不同,但目标一致:在高频交互中守住性能底线,不让系统被“压垮”

  • 当你只关心用户的最终意图(如输入完成、窗口调整结束),请用防抖
  • 当你需要持续响应但必须控制节奏(如滚动、拖拽、射击),请选择节流

它们不是炫技的语法糖,而是应对真实场景的工程智慧。掌握何时用、怎么用,不仅能写出更健壮的代码,也能为用户带来更流畅、更可靠的体验。
在追求极致性能的前端世界里,防抖与节流,正是那“四两拨千斤”的关键一招。