防抖(debounce)

一、防抖的概念

防抖:当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次。如果设定的时间到来之前又一次触发了事件,就重新开始计时。

典型应用场景

  • 搜索框输入联想(等待用户停止输入后再请求)
  • 窗口大小调整(等待调整结束后再计算布局)
  • 按钮防重复点击

二、基础实现思路

  1. 基本需求

    • 延迟执行函数
    • 如果在延迟期间再次触发,则取消前一次的执行
    • 只执行最后一次触发
  2. 实现要素

    • 需要一个定时器来管理延迟执行
    • 需要保存函数执行的上下文(this)和参数
    • 需要清除前一个定时器的能力

三、一步步实现

第一步:基础框架

javascript 复制代码
function debounce(func, wait) {
  let timeout;
  
  return function() {
    // 保存上下文和参数
    const context = this;
    const args = arguments;
    
    // 清除之前的定时器
    clearTimeout(timeout);
    
    // 设置新的定时器
    timeout = setTimeout(function() {
      func.apply(context, args);
    }, wait);
  };
}

第二步:立即执行选项

有时候我们需要第一次触发时立即执行,然后等待停止触发后再恢复。

javascript 复制代码
function debounce(func, wait, immediate) {
  let timeout;
  
  return function() {
    const context = this;
    const args = arguments;
    
    // 如果已经设置过定时器则清除
    if (timeout) clearTimeout(timeout);
    
    // 立即执行的情况
    if (immediate) {
      // 如果还没有执行过,则立即执行
      const callNow = !timeout;
      // 设置定时器,在等待期结束后重置timeout
      timeout = setTimeout(function() {
        timeout = null;
      }, wait);
      if (callNow) func.apply(context, args);
    } 
    // 非立即执行的情况
    else {
      timeout = setTimeout(function() {
        func.apply(context, args);
      }, wait);
    }
  };
}

第三步:取消功能

有时候我们需要能够在等待期间手动取消防抖。

javascript 复制代码
function debounce(func, wait, immediate) {
  let timeout;
  
  // 返回的防抖函数
  const debounced = function() {
    const context = this;
    const args = arguments;
    
    if (timeout) clearTimeout(timeout);
    
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(function() {
        timeout = null;
      }, wait);
      if (callNow) func.apply(context, args);
    } else {
      timeout = setTimeout(function() {
        func.apply(context, args);
      }, wait);
    }
  };
  
  // 添加取消方法
  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
  };
  
  return debounced;
}

第四步:返回值处理

当被防抖的函数有返回值时,我们需要处理返回值。对于立即执行的情况,可以返回函数执行结果。

javascript 复制代码
function debounce(func, wait, immediate) {
  let timeout, result;
  
  const debounced = function() {
    const context = this;
    const args = arguments;
    
    if (timeout) clearTimeout(timeout);
    
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(function() {
        timeout = null;
      }, wait);
      if (callNow) result = func.apply(context, args);
    } else {
      timeout = setTimeout(function() {
        func.apply(context, args);
      }, wait);
    }
    
    return result;
  };
  
  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
  };
  
  return debounced;
}

第五步:完整实现(带注释)

javascript 复制代码
/**
 * 防抖函数
 * @param {Function} func 需要防抖的函数
 * @param {number} wait 等待时间(毫秒)
 * @param {boolean} immediate 是否立即执行
 * @return {Function} 返回防抖后的函数
 */
function debounce(func, wait, immediate) {
  let timeout, result;
  
  const debounced = function() {
    const context = this;
    const args = arguments;
    
    // 如果已有定时器存在,则清除
    if (timeout) clearTimeout(timeout);
    
    // 立即执行模式
    if (immediate) {
      // 如果定时器不存在,表示可以立即执行
      const callNow = !timeout;
      // 设置定时器,在等待时间后重置timeout
      timeout = setTimeout(() => {
        timeout = null;
      }, wait);
      // 如果允许立即执行,则调用函数
      if (callNow) result = func.apply(context, args);
    } 
    // 非立即执行模式
    else {
      // 设置定时器,在等待时间后执行函数
      timeout = setTimeout(() => {
        func.apply(context, args);
      }, wait);
    }
    
    return result;
  };
  
  // 添加取消方法
  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
  };
  
  return debounced;
}

四、使用示例

1. 基本使用

javascript 复制代码
// 需要防抖的函数
function search(query) {
  console.log('搜索:', query);
}

// 创建防抖函数
const debouncedSearch = debounce(search, 500);

// 模拟连续输入
debouncedSearch('a');
debouncedSearch('ab');
debouncedSearch('abc');
// 只有最后一次调用会在500ms后执行,输出: 搜索: abc

2. 立即执行模式

javascript 复制代码
function buttonClick() {
  console.log('按钮点击');
}

const debouncedClick = debounce(buttonClick, 1000, true);

// 第一次点击会立即执行
debouncedClick(); // 立即输出: 按钮点击
// 1秒内连续点击不会触发
debouncedClick();
debouncedClick();
// 1秒后再次点击会再次立即执行

3. 取消防抖

javascript 复制代码
function saveData() {
  console.log('数据已保存');
}

const debouncedSave = debounce(saveData, 2000);

debouncedSave(); // 开始计时
// 1秒后取消
setTimeout(() => {
  debouncedSave.cancel();
  console.log('保存已取消');
}, 1000);
// 不会输出"数据已保存"

五、实际应用场景

1. 搜索框联想

javascript 复制代码
const searchInput = document.getElementById('search-input');

function fetchSuggestions(query) {
  // 发送请求获取搜索建议
  console.log('获取建议:', query);
}

const debouncedFetch = debounce(fetchSuggestions, 300);

searchInput.addEventListener('input', function(e) {
  debouncedFetch(e.target.value);
});

2. 窗口大小调整

javascript 复制代码
function handleResize() {
  console.log('窗口大小:', window.innerWidth, window.innerHeight);
}

window.addEventListener('resize', debounce(handleResize, 250));

3. 按钮防重复点击

javascript 复制代码
const submitBtn = document.getElementById('submit-btn');

function submitForm() {
  console.log('表单提交');
  // 实际提交逻辑...
}

submitBtn.addEventListener('click', debounce(submitForm, 1000, true));

六、与节流(throttle)的区别

特性 防抖(debounce) 节流(throttle)
执行时机 停止触发后延迟执行 固定时间间隔执行
重置机制 每次触发都会重置计时器 计时器期间新触发会被忽略
适用场景 搜索联想、窗口resize结束后的操作 滚动事件、鼠标移动事件等频繁触发

七、TypeScript 版本实现

typescript 复制代码
interface DebouncedFunction<T extends (...args: any[]) => any> {
  (...args: Parameters<T>): ReturnType<T> | undefined;
  cancel: () => void;
}

function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number,
  immediate?: boolean
): DebouncedFunction<T> {
  let timeout: ReturnType<typeof setTimeout> | null;
  let result: ReturnType<T> | undefined;
  
  const debounced = function(this: any, ...args: Parameters<T>) {
    const context = this;
    
    if (timeout) clearTimeout(timeout);
    
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait);
      if (callNow) result = func.apply(context, args);
    } else {
      timeout = setTimeout(() => {
        func.apply(context, args);
      }, wait);
    }
    
    return result;
  } as DebouncedFunction<T>;
  
  debounced.cancel = () => {
    if (timeout) {
      clearTimeout(timeout);
      timeout = null;
    }
  };
  
  return debounced;
}

八、总结

  1. 防抖核心:通过定时器延迟执行,在频繁触发时只执行最后一次
  2. 关键点
    • 使用闭包保存定时器变量
    • 正确处理执行上下文(this)和参数
    • 提供取消功能增强灵活性
    • 可选立即执行模式满足不同需求
  3. 应用场景:高频事件但只需要最终状态的场景
  4. 注意事项
    • 合理设置等待时间
    • 注意内存泄漏问题(及时取消)
    • 对于需要返回值的情况要特殊处理
相关推荐
前端南玖7 分钟前
深入Vue3响应式:手写实现reactive与ref
前端·javascript·vue.js
wordbaby28 分钟前
React Router 双重加载器机制:服务端 loader 与客户端 clientLoader 完整解析
前端·react.js
itslife32 分钟前
Fiber 架构
前端·react.js
3Katrina35 分钟前
妈妈再也不用担心我的课设了---Vibe Coding帮你实现期末课设!
前端·后端·设计
hubber35 分钟前
一次 SPA 架构下的性能优化实践
前端
可乐只喝可乐1 小时前
从0到1构建一个Agent智能体
前端·typescript·agent
Muxxi1 小时前
shopify模板开发
前端
Yueyanc1 小时前
LobeHub桌面应用的IPC通信方案解析
前端·javascript
我是若尘2 小时前
利用资源提示关键词优化网页加载速度
前端
moyu842 小时前
跨域问题解析(下):Nginx代理、domain修改与postMessage解决方案
前端