JS 防抖与节流

防抖

核心思想:延迟执行 ,只有在事件触发的频率降低到一定程度后才会执行,而且如果事件持续触发,之前地所有执行都会被取消。

有的操作是高频触发的,但是其实只需要一次触发。比如短时间内多次缩放页面resize,我们不应该每次缩放都去执行操作,应该只做一次。再比如监听输入框的输入,不应每次输入框内容变化都触发监听,应该是用户完成一段输入后再进行触发。

防抖就是为了避免事件重复触发。

具体步骤: 每当事件触发 就 开一个定时器 。如果再次触发,清除之前该事件的定时器,重设一个。直到定时器到时,触发操作。

示例:

javascript 复制代码
function debounce(func, delay = 300) {
  let timer;
  
  // 返回一个新的函数,该函数在被调用时会延迟执行 func
  return function(...args) {
    // 保存当前 this 上下文(确保 func 的 this 正确)
    const context = this;
    
    // 清除之前的定时器,重置为最新触发
    if (timer) clearTimeout(timer);
    
    // 设置新的定时器,延迟执行目标函数
    timer = setTimeout(() => {
      func.apply(context, args); // 使用 apply 传递 this 和参数
    }, delay);
  };
}

// 使用示例:输入框搜索防抖
const input = document.querySelector('input');
input.addEventListener('input', debounce((e) => {
  console.log('搜索内容:', e.target.value);
}, 500));

防抖的变种:立即执行

在某些情况下,我们希望在事件第一次触发时立即执行函数,之后再按照防抖的规则进行处理。可以通过增加一个参数来实现这个功能:

javascript 复制代码
function debounce(func, delay = 300, immediate = false) {
    let timer;
    return function (...args) {
        const context = this;
        const isFirstCall = !timer;
        // 清除之前的定时器
        clearTimeout(timer);
        if (immediate && isFirstCall) {
            // 第一次触发且设置了立即执行,则立即执行函数
            func.apply(context, args);
        }
        // 设置新的定时器
        timer = setTimeout(() => {
            timer = null;
            if (!immediate) {
                // 如果没有设置立即执行,则在延迟时间后执行函数
                func.apply(context, args);
            }
        }, delay);
    };
}

// 使用示例
function clickHandler() {
    console.log('按钮被点击');
}

const button = document.getElementById('myButton');
// 对 clickHandler 函数进行防抖处理,延迟时间为 1000 毫秒,立即执行
button.addEventListener('click', debounce(clickHandler, 1000, true));

节流

**防抖存在的问题:**事件会一直等到用户完成操作后一段时间在操作,如果一直操作,会一直不触发。比如说是一个按钮,点击就发送请求,如果一直点,那么请求就会一直发布出去。这里正确的思路应该是第一次点击就发送,然后上一个请求回来后,才能再发。

核心思想:间隔执行,保证在特定时间内至少执行一次,无论事件触发频率如何。某个操作希望上一次的完成后再进行下一次,或者希望隔一段时间触发一次。节流就是减少流量,将频繁触发的事件减少,并每隔一段时间执行。即,控制事件触发的频率

执行步骤 :事件触发之后,执行操作,然后设置一个定时器,在设定时间内相同的事件触发无效,过了定时器的时间后,操作才可以再次触发。

节流方式一:时间戳方式

javascript 复制代码
function throttle(func, interval) {
    let lastTime = 0;
    return function (...args) {
        const context = this;
        const now = Date.now();
        if (now - lastTime >= interval) {
            func.apply(context, args);
            lastTime = now;
        }
    };
}

// 使用示例
function handleScroll() {
    console.log('滚动事件处理');
}

const scrollArea = document.getElementById('scrollArea');
// 对 handleScroll 函数进行节流处理,时间间隔为 500 毫秒
scrollArea.addEventListener('scroll', throttle(handleScroll, 500));
  • throttle 函数接收两个参数,func 是需要进行节流处理的函数,interval 是时间间隔。
  • 利用 lastTime 变量记录上一次函数执行的时间。
  • 每次事件触发时,获取当前时间 now,通过计算 now - lastTime 判断是否超过了设定的时间间隔。如果超过了,就执行函数并更新 lastTime

节流方式二:定时器实现方式

javascript 复制代码
function throttle(func, interval) {
    let timer = null;
    return function (...args) {
        const context = this;
        if (!timer) {
            func.apply(context, args);
            timer = setTimeout(() => {
                timer = null;
            }, interval);
        }
    };
}

// 使用示例
function handleClick() {
    console.log('按钮点击处理');
}

const button = document.getElementById('button');
// 对 handleClick 函数进行节流处理,时间间隔为 1000 毫秒
button.addEventListener('click', throttle(handleClick, 1000));

节流方式三:时间戳和定时器结合

javascript 复制代码
function throttle(func, interval) {
    let lastTime = 0;
    let timer = null;
    return function (...args) {
        const context = this;
        const now = Date.now();
        const remaining = interval - (now - lastTime);

        clearTimeout(timer);

        if (remaining <= 0) {
            func.apply(context, args);
            lastTime = now;
        } else {
            timer = setTimeout(() => {
                func.apply(context, args);
                lastTime = Date.now();
            }, remaining);
        }
    };
}

这种结合方式综合了时间戳和定时器的优点,既能保证在开始时立即执行函数,又能在后续按照固定时间间隔执行。

综合示例:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Debounce vs Throttle 可视化演示</title>
  <style>
    body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
    .demo-box { border: 2px solid #ddd; padding: 20px; margin: 20px 0; border-radius: 8px; }
    .stats { background: #f5f5f5; padding: 10px; margin: 10px 0; }
    .interactive-area { height: 150px; border: 2px dashed #999; margin: 10px 0; display: flex; align-items: center; justify-content: center; }
  </style>
</head>
<body>
  <h1>防抖(Debounce) vs 节流(Throttle)</h1>

  <!-- 输入框防抖演示 -->
  <div class="demo-box">
    <h3>1. 输入框搜索 - 防抖演示</h3>
    <input type="text" id="searchInput" placeholder="输入内容观察防抖效果">
    <div class="stats">
      输入事件触发: <span id="inputCount">0</span> 次,
      实际处理: <span id="inputProcessed">0</span> 次
    </div>
  </div>

  <!-- 按钮点击防抖/节流演示 -->
  <div class="demo-box">
    <h3>2. 按钮点击 - 防抖 vs 节流</h3>
    <button id="debounceBtn">防抖按钮(1秒内只响应首次)</button>
    <button id="throttleBtn">节流按钮(1秒内只响应一次)</button>
    <div class="stats">
      防抖点击: <span id="debounceClick">0</span> 次,
      节流点击: <span id="throttleClick">0</span> 次
    </div>
  </div>

  <!-- 滚动节流演示 -->
  <div class="demo-box">
    <h3>3. 滚动事件 - 节流演示</h3>
    <div class="interactive-area" id="scrollArea">滚动此区域</div>
    <div class="stats">
      滚动事件触发: <span id="scrollCount">0</span> 次,
      实际处理: <span id="scrollProcessed">0</span> 次
    </div>
  </div>

  <!-- 鼠标移动防抖演示 -->
  <div class="demo-box">
    <h3>4. 鼠标移动 - 防抖 vs 节流</h3>
    <div class="interactive-area" id="mouseArea">在此移动鼠标</div>
    <div class="stats">
      防抖处理: <span id="debounceMouse">0</span> 次,
      节流处理: <span id="throttleMouse">0</span> 次
    </div>
  </div>

  <script>
    // ================= 工具函数 =================
    // 防抖函数(支持立即执行)
    function debounce(func, delay = 300, immediate = false) {
      let timer;
      return function(...args) {
        const context = this;
        const isFirstCall = !timer;
        
        clearTimeout(timer);
        if (immediate && isFirstCall) func.apply(context, args);
        
        timer = setTimeout(() => {
          timer = null;
          if (!immediate) func.apply(context, args);
        }, delay);
      };
    }

    // 节流函数(结合时间戳和定时器)
    function throttle(func, interval = 300) {
      let lastTime = 0, timer;
      return function(...args) {
        const context = this;
        const now = Date.now();
        const remaining = interval - (now - lastTime);

        clearTimeout(timer);
        
        if (remaining <= 0) {
          func.apply(context, args);
          lastTime = now;
        } else if (!timer) {
          timer = setTimeout(() => {
            func.apply(context, args);
            lastTime = Date.now();
            timer = null;
          }, remaining);
        }
      };
    }

    // ================= 示例逻辑 =================
    // 1. 输入框防抖
    const searchInput = document.getElementById('searchInput');
    let inputCount = 0, inputProcessed = 0;

    searchInput.addEventListener('input', () => {
      document.getElementById('inputCount').textContent = ++inputCount;
    });

    const processSearch = debounce(() => {
      document.getElementById('inputProcessed').textContent = ++inputProcessed;
    }, 500);
    searchInput.addEventListener('input', processSearch);

    // 2. 按钮点击
    const debounceBtn = document.getElementById('debounceBtn');
    const throttleBtn = document.getElementById('throttleBtn');
    let debounceClick = 0, throttleClick = 0;

    debounceBtn.addEventListener('click', debounce(() => {
      document.getElementById('debounceClick').textContent = ++debounceClick;
    }, 1000, true));

    throttleBtn.addEventListener('click', throttle(() => {
      document.getElementById('throttleClick').textContent = ++throttleClick;
    }, 1000));

    // 3. 滚动节流
    const scrollArea = document.getElementById('scrollArea');
    let scrollCount = 0, scrollProcessed = 0;

    scrollArea.addEventListener('scroll', () => {
      document.getElementById('scrollCount').textContent = ++scrollCount;
    });

    scrollArea.addEventListener('scroll', throttle(() => {
      document.getElementById('scrollProcessed').textContent = ++scrollProcessed;
    }, 500));

    // 4. 鼠标移动对比
    const mouseArea = document.getElementById('mouseArea');
    let debounceMouse = 0, throttleMouse = 0;

    // 防抖处理(延迟200ms)
    mouseArea.addEventListener('mousemove', debounce(() => {
      document.getElementById('debounceMouse').textContent = ++debounceMouse;
    }, 200));

    // 节流处理(每200ms执行一次)
    mouseArea.addEventListener('mousemove', throttle(() => {
      document.getElementById('throttleMouse').textContent = ++throttleMouse;
    }, 200));
  </script>
</body>
</html>

下面对上述代码中的四个例子分别进行分析:

1.输入框搜索-防抖演示
javascript 复制代码
// 获取输入框元素
const searchInput = document.getElementById('searchInput');
// 记录输入事件触发的次数
let inputCount = 0;
// 记录实际处理的次数
let inputProcessed = 0;

// 监听输入框的 input 事件,每次触发时更新输入事件触发的次数
searchInput.addEventListener('input', () => {
  document.getElementById('inputCount').textContent = ++inputCount;
});

// 创建一个经过防抖处理的函数
const processSearch = debounce(() => {
  // 每次执行时更新实际处理的次数
  document.getElementById('inputProcessed').textContent = ++inputProcessed;
}, 500);
// 监听输入框的 input 事件,使用经过防抖处理的函数
searchInput.addEventListener('input', processSearch);
  • 功能 :演示输入框输入时的防抖效果。当用户在输入框中输入内容时,input 事件会频繁触发,inputCount 会实时记录触发次数。而 processSearch 是经过防抖处理的函数,它会在用户停止输入 500 毫秒后才执行,inputProcessed 记录实际处理的次数。
  • 原理 :使用 debounce 函数对输入事件处理函数进行包装,确保在用户停止输入一段时间后才执行实际处理逻辑,避免不必要的频繁处理。
2. 按钮点击防抖/节流演示
javascript 复制代码
// 获取防抖按钮元素
const debounceBtn = document.getElementById('debounceBtn');
// 获取节流按钮元素
const throttleBtn = document.getElementById('throttleBtn');
// 记录防抖按钮点击的次数
let debounceClick = 0;
// 记录节流按钮点击的次数
let throttleClick = 0;

// 监听防抖按钮的 click 事件,使用经过防抖处理的函数
debounceBtn.addEventListener('click', debounce(() => {
  // 每次执行时更新防抖按钮点击的次数
  document.getElementById('debounceClick').textContent = ++debounceClick;
}, 1000, true));

// 监听节流按钮的 click 事件,使用经过节流处理的函数
throttleBtn.addEventListener('click', throttle(() => {
  // 每次执行时更新节流按钮点击的次数
  document.getElementById('throttleClick').textContent = ++throttleClick;
}, 1000));
  • 功能 :演示按钮点击时的防抖和节流效果。debounceBtn 是防抖按钮,在 1 秒内多次点击,只有首次点击会立即响应;throttleBtn 是节流按钮,在 1 秒内无论点击多少次,只会响应一次。
  • 原理
    • 防抖 :使用 debounce 函数对按钮点击事件处理函数进行包装,设置 immediatetrue 表示首次点击立即执行,后续点击会在 1 秒内无新点击时才执行。
    • 节流 :使用 throttle 函数对按钮点击事件处理函数进行包装,确保在 1 秒内最多执行一次处理函数。

3. 滚动节流演示

javascript 复制代码
// 获取滚动区域元素
const scrollArea = document.getElementById('scrollArea');
// 记录滚动事件触发的次数
let scrollCount = 0;
// 记录实际处理的次数
let scrollProcessed = 0;

// 监听滚动区域的 scroll 事件,每次触发时更新滚动事件触发的次数
scrollArea.addEventListener('scroll', () => {
  document.getElementById('scrollCount').textContent = ++scrollCount;
});

// 监听滚动区域的 scroll 事件,使用经过节流处理的函数
scrollArea.addEventListener('scroll', throttle(() => {
  // 每次执行时更新实际处理的次数
  document.getElementById('scrollProcessed').textContent = ++scrollProcessed;
}, 500));
  • 功能 :演示滚动事件的节流效果。当用户滚动 scrollArea 区域时,scroll 事件会频繁触发,scrollCount 会实时记录触发次数。而经过节流处理的函数会确保每 500 毫秒最多执行一次,scrollProcessed 记录实际处理的次数。
  • 原理 :使用 throttle 函数对滚动事件处理函数进行包装,通过时间戳和定时器结合的方式,控制处理函数的执行频率,避免频繁处理滚动事件。

4. 鼠标移动防抖演示

javascript 复制代码
// 获取鼠标移动区域元素
const mouseArea = document.getElementById('mouseArea');
// 记录鼠标移动防抖处理的次数
let debounceMouse = 0;
// 记录鼠标移动节流处理的次数
let throttleMouse = 0;

// 监听鼠标移动区域的 mousemove 事件,使用经过防抖处理的函数
mouseArea.addEventListener('mousemove', debounce(() => {
  // 每次执行时更新鼠标移动防抖处理的次数
  document.getElementById('debounceMouse').textContent = ++debounceMouse;
}, 200));

// 监听鼠标移动区域的 mousemove 事件,使用经过节流处理的函数
mouseArea.addEventListener('mousemove', throttle(() => {
  // 每次执行时更新鼠标移动节流处理的次数
  document.getElementById('throttleMouse').textContent = ++throttleMouse;
}, 200));
  • 功能 :演示鼠标移动时的防抖和节流效果。当用户在 mouseArea 区域移动鼠标时,mousemove 事件会频繁触发。经过防抖处理的函数会在用户停止移动鼠标 200 毫秒后执行,而经过节流处理的函数会确保每 200 毫秒最多执行一次。
  • 原理
    • 防抖 :使用 debounce 函数对鼠标移动事件处理函数进行包装,确保在用户停止移动鼠标一段时间后才执行实际处理逻辑。
    • 节流 :使用 throttle 函数对鼠标移动事件处理函数进行包装,控制处理函数的执行频率,避免频繁处理鼠标移动事件。
相关推荐
m0_736927044 小时前
2025高频Java后端场景题汇总(全年汇总版)
java·开发语言·经验分享·后端·面试·职场和发展·跳槽
宇余4 小时前
从 useState 到 URLState:前端状态管理的另一种思路
前端·vue.js
白兰地空瓶4 小时前
🚀 10 分钟吃透 CSS position 定位!从底层原理到避坑实战,搞定所有布局难题
前端·css
FAREWELL000754 小时前
Lua学习记录(3) --- Lua中的复杂数据类型_table
开发语言·学习·lua
T___T4 小时前
Ajax 数据请求详解与实战
javascript·面试
onthewaying4 小时前
在Android平台上使用Three.js优雅的加载3D模型
android·前端·three.js
IT北辰4 小时前
Python实现居民供暖中暖气能耗数据可视化分析(文中含源码)
开发语言·python·信息可视化
冴羽5 小时前
能让 GitHub 删除泄露的苹果源码还有 8000 多个相关仓库的 DMCA 是什么?
前端·javascript·react.js
悟能不能悟5 小时前
jsp怎么拿到url参数
java·前端·javascript
KWTXX5 小时前
组合逻辑和时序逻辑的区别
java·开发语言·人工智能