防抖与节流

396 阅读4分钟

防抖与节流:前端性能优化的关键技术

在现代 Web 开发中,用户交互频繁,某些事件(如点击、滚动、输入等)可能被快速连续触发。如果不加以控制,这些高频操作可能导致性能问题,甚至引发服务器压力过大。为此,我们引入了两种重要的函数控制技术:防抖(Debounce)节流(Throttle)

本文将通过实际案例,深入浅出地讲解它们的原理、实现方式以及应用场景。


一、为什么要使用防抖?

设想一个场景:用户点击“提交”按钮,请求立即发送到后端。如果此时有大量用户同时点击,服务器可能会因瞬时请求激增而崩溃。

因此,我们需要对用户的操作进行限制,避免重复提交或高频触发。


二、什么是防抖(Debounce)?

防抖的核心思想是:
当事件被触发时,设置一个延迟执行的任务;如果在延迟时间内事件再次被触发,则清除之前的任务,重新计时。
只有当用户停止操作一段时间后,函数才会真正执行。

正确实现:清除旧定时器

function debounce(fn, wait) {
    let time = null;
    const debounced = function (...args) {
        if (time) clearTimeout(time);
        time = setTimeout(() => {
            fn.apply(this, args);
            time = null;
        }, wait)
    }
    // 预留取消钩子
    debounced.cancel = () => {
        if (timer) clearTimeout(timer), timer = null;
    };
    return debounced;
}

效果

  • 用户连续点击时,每次都会清除前一个定时器。
  • 只有当用户停止点击 超过 wait 毫秒 后,fn() 才会执行一次。

🔧 适用场景

  • 搜索框输入联想(避免每次输入都发请求)
  • 表单提交按钮防重复提交
  • 窗口 resize 事件处理(避免频繁重排)

三、什么是节流(Throttle)?

与防抖不同,节流的核心思想是“稀释”执行频率
在一定时间窗口内,无论事件触发多少次,函数最多只执行一次。

例如:设置 1 秒内最多执行一次函数,即使用户点击了 10 次,也只响应一次。

节流实现方式一:时间戳对比

function throttle(fn,wait){
    let time = 0;
    return function(...args){
        let nowtime = Date.now();
        if(nowtime - time > wait){
            fn.apply(this,args);
            time = nowtime;
        }
    }
}

特点

  • 第一次点击立即执行。
  • wait 时间内再次点击无效。
  • 超过 wait 后,下一次触发即可执行。

节流实现方式二:定时器控制(可选)

function throttle(fn, wait) {
  let timer = null;

  return function () {
    if (timer) return; // 定时器存在,说明还在冷却期

    timer = setTimeout(() => {
      fn();
      clearTimeout(timer);
      timer = null;
    }, wait);
  };
}

⚠️ 注意:这种方式首次不会立即执行,适合延迟执行的场景。

🔧 适用场景

  • 页面滚动事件(scroll)监听
  • 鼠标移动事件(mousemove
  • 游戏按键响应(防止技能连发)
  • 监听 resizeinput 但需控制频率

四、防抖 vs 节流:关键区别

防抖和节流是前端开发中常用的性能优化手段,主要用于控制高频事件的触发频率。它们的核心区别在于执行时机的控制方式不同,但本质都是通过限制函数执行次数来提升性能。

防抖的核心逻辑是延迟执行,它会设置一个定时器,在事件触发后等待指定时间再执行目标函数。如果在等待期间事件再次触发,就会清除之前的定时器重新计时。只有当事件停止触发一段时间后,函数才会真正执行。这种机制特别适合处理搜索框输入、窗口调整等场景,可以有效避免频繁触发导致的性能问题。

节流的核心逻辑是固定频率执行,它会记录上次执行时间,当事件再次触发时,只有距离上次执行时间超过设定间隔才会执行目标函数。这种机制保证了无论事件触发多么频繁,函数都会按照固定节奏执行,特别适合处理滚动事件、鼠标移动等高频触发的场景。

这两种优化方法都利用了闭包机制来保存状态信息。防抖通过闭包保存定时器ID,节流通过闭包保存上次执行时间戳。在实际应用中,需要根据具体场景选择合适的方法:需要事件停止后才执行的用防抖,需要固定间隔执行的用节流。

📌 建议:在实际开发中,优先考虑使用成熟的工具库(如 Lodash)中的 _.debounce_.throttle,它们已对边界情况做了充分处理。