您现在的位置是:网站首页 > 防抖(Debounce)与节流(Throttle)模式的事件处理文章详情

防抖(Debounce)与节流(Throttle)模式的事件处理

防抖和节流是两种优化高频事件处理的技术,能有效控制事件触发的频率,避免性能问题。它们在前端开发中应用广泛,尤其在滚动、窗口调整、输入框实时搜索等场景下。

防抖(Debounce)模式

防抖的核心思想是:在事件被触发后,延迟执行回调函数。如果在延迟期间事件再次被触发,则重新计时。只有当事件停止触发一段时间后,回调才会真正执行。

基本实现

function debounce(func, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

应用场景

  1. 搜索框输入:用户连续输入时不立即请求,只在停止输入300ms后发起搜索
  2. 窗口大小调整:避免resize事件频繁触发布局计算
  3. 表单验证:不在每次输入时验证,只在用户停止输入后验证

立即执行版本

有时我们需要第一次触发立即执行,后续触发才防抖:

function debounceImmediate(func, delay, immediate) {
  let timer;
  return function(...args) {
    const context = this;
    const callNow = immediate && !timer;
    
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = null;
      if (!immediate) func.apply(context, args);
    }, delay);
    
    if (callNow) func.apply(context, args);
  };
}

节流(Throttle)模式

节流的核心思想是:在一定时间间隔内,只执行一次回调函数。无论事件触发多么频繁,都会按照固定频率执行。

基本实现

function throttle(func, limit) {
  let inThrottle;
  return function(...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

应用场景

  1. 滚动事件:滚动时每100ms检查一次位置,而不是每次滚动都触发
  2. 鼠标移动:拖拽元素时限制位置更新的频率
  3. 动画渲染:保证动画帧率稳定,避免过度渲染

时间戳版本

另一种实现方式是使用时间戳比较:

function throttleTimestamp(func, limit) {
  let lastTime;
  return function(...args) {
    const now = Date.now();
    if (!lastTime || now - lastTime >= limit) {
      func.apply(this, args);
      lastTime = now;
    }
  };
}

防抖与节流的比较

特性 防抖 节流
执行时机 事件停止后执行 固定间隔执行
适用场景 关注最终状态 关注过程状态
连续触发效果 只执行最后一次 均匀执行
典型应用 搜索建议、窗口resize 滚动加载、游戏按键

高级应用与变体

组合使用

某些场景需要先防抖再节流:

function debounceThrottle(func, debounceTime, throttleTime) {
  let lastCall = 0;
  let timer;
  
  return function(...args) {
    const now = Date.now();
    clearTimeout(timer);
    
    if (now - lastCall >= throttleTime) {
      lastCall = now;
      func.apply(this, args);
    } else {
      timer = setTimeout(() => {
        lastCall = now;
        func.apply(this, args);
      }, debounceTime);
    }
  };
}

requestAnimationFrame节流

对于动画场景,可以使用rAF实现更流畅的节流:

function throttleRAF(func) {
  let isRunning;
  return function(...args) {
    if (!isRunning) {
      isRunning = true;
      requestAnimationFrame(() => {
        func.apply(this, args);
        isRunning = false;
      });
    }
  };
}

实际项目中的考量

  1. 取消机制:实现可取消的防抖/节流函数
  2. 返回值处理:处理带返回值的函数调用
  3. leading/trailing:控制是否在时间间隔开始或结束时触发
  4. 最大等待时间:设置防抖的最大延迟上限
function advancedDebounce(func, wait, options = {}) {
  let timeout, context, args, result;
  let previous = 0;
  
  const later = function() {
    previous = options.leading === false ? 0 : Date.now();
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  
  const debounced = function() {
    const now = Date.now();
    if (!previous && options.leading === false) previous = now;
    const remaining = wait - (now - previous);
    context = this;
    args = arguments;
    
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
  
  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = context = args = null;
  };
  
  return debounced;
}

在现代框架中的应用

React中的实现

import { useCallback, useEffect, useRef } from 'react';

function useDebounce(callback, delay) {
  const timeoutRef = useRef();
  
  useEffect(() => {
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);
  
  return useCallback((...args) => {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
    
    timeoutRef.current = setTimeout(() => {
      callback(...args);
    }, delay);
  }, [callback, delay]);
}

Vue中的实现

import { customRef } from 'vue';

export function useDebouncedRef(value, delay = 200) {
  let timeout;
  return customRef((track, trigger) => {
    return {
      get() {
        track();
        return value;
      },
      set(newValue) {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
          value = newValue;
          trigger();
        }, delay);
      }
    };
  });
}

性能优化实践

  1. 合理设置延迟时间:根据场景选择适当的值(搜索建议200-300ms,滚动50-100ms)
  2. 避免内存泄漏:在组件卸载时清除定时器
  3. 批量处理事件:对高频事件进行批量处理而非单个处理
  4. 使用Passive事件监听器:特别是touch/wheel事件
// 优化滚动性能
window.addEventListener('scroll', throttle(handleScroll, 100), { passive: true });

常见误区与陷阱

  1. 过度使用防抖:可能导致界面响应迟钝
  2. 忽略this绑定:在回调函数中丢失正确的this指向
  3. 定时器未清除:引起内存泄漏
  4. 忽略返回值:防抖/节流后的函数返回值可能不符合预期
  5. 时间间隔设置不当:太短达不到效果,太长影响用户体验

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步