TypeScript 防抖函数封装:原理与实现

防抖(Debounce)是前端开发中常用的性能优化技术,特别是在处理频繁触发的事件时非常有用。本文将介绍防抖的概念、原理,并通过 TypeScript 实现一个类型安全的防抖函数。

什么是防抖?

防抖是一种限制函数执行频率的技术。它的核心思想是:在一定时间内,无论触发多少次事件,都只执行最后一次。这在处理如窗口大小调整、输入框输入、滚动事件等高频触发场景时特别有用。

防抖的应用场景

  1. 搜索框输入联想(避免每次输入都发送请求)
  2. 窗口大小调整事件
  3. 滚动事件处理
  4. 按钮频繁点击防止重复提交

TypeScript 实现防抖函数

下面我们来实现一个类型安全的防抖函数:

代码语言:javascript代码运行次数:0运行复制
/**
 * 防抖函数封装
 * @param fn 需要防抖的函数
 * @param delay 防抖延迟时间(毫秒)
 * @param immediate 是否立即执行
 * @returns 包装后的防抖函数
 */
function debounce<T extends (...args: any[]) => any>(
  fn: T,
  delay: number,
  immediate: boolean = false
): (...args: Parameters<T>) => void {
  let timer: ReturnType<typeof setTimeout> | null = null;
  
  return function(this: ThisParameterType<T>, ...args: Parameters<T>) {
    const context = this;
    
    if (timer) {
      clearTimeout(timer);
    }
    
    if (immediate && !timer) {
      fn.apply(context, args);
    }
    
    timer = setTimeout(() => {
      if (!immediate) {
        fn.apply(context, args);
      }
      timer = null;
    }, delay);
  };
}
代码解析
  1. 泛型定义:使用泛型 T 来保持原始函数的类型签名,确保类型安全
  2. 参数类型
  • fn: T:要防抖的函数
  • delay: number:防抖延迟时间(毫秒)
  • immediate: boolean:是否立即执行(默认为 false)
  1. 内部变量
  • timer:存储 setTimeout 的返回值,用于清除定时器
  1. 返回函数
  • 保持原始函数的 this 上下文和参数类型
  • 每次调用都会清除之前的定时器
  • 根据 immediate 参数决定是立即执行还是延迟执行

使用示例

基本用法
代码语言:javascript代码运行次数:0运行复制
// 普通函数
function search(query: string) {
  console.log(`Searching for: ${query}`);
}

const debouncedSearch = debounce(search, 300);

// 快速连续调用
debouncedSearch("a");
debouncedSearch("ab");
debouncedSearch("abc");
// 最终只会输出: "Searching for: abc"
带立即执行的防抖
代码语言:javascript代码运行次数:0运行复制
function submitForm(data: { name: string; age: number }) {
  console.log("Submitting:", data);
}

const debouncedSubmit = debounce(submitForm, 500, true);

// 第一次调用会立即执行
debouncedSubmit({ name: "Alice", age: 25 });
// 快速连续调用
debouncedSubmit({ name: "Bob", age: 30 });
debouncedSubmit({ name: "Charlie", age: 35 });
// 最终会在延迟后执行最后一次
类方法中的使用
代码语言:javascript代码运行次数:0运行复制
class SearchService {
  private debouncedSearch: (query: string) => void;

  constructor() {
    this.debouncedSearch = debounce(this.search.bind(this), 300);
  }

  private search(query: string) {
    console.log(`Performing search: ${query}`);
    // 实际搜索逻辑...
  }

  public handleInput(query: string) {
    this.debouncedSearch(query);
  }
}

const service = new SearchService();
service.handleInput("t");
service.handleInput("ty");
service.handleInput("typ");
service.handleInput("types");
// 最终只会执行一次搜索: "Performing search: types"

高级改进:取消功能和返回值处理

我们可以进一步增强防抖函数,添加取消功能和 Promise 返回值支持:

代码语言:javascript代码运行次数:0运行复制
interface DebouncedFunction<T extends (...args: any[]) => any> {
  (...args: Parameters<T>): Promise<ReturnType<T>>;
  cancel: () => void;
}

function advancedDebounce<T extends (...args: any[]) => any>(
  fn: T,
  delay: number,
  immediate: boolean = false
): DebouncedFunction<T> {
  let timer: ReturnType<typeof setTimeout> | null = null;
  let resolveFn: ((value: ReturnType<T>) => void) | null = null;
  
  const debounced = function(this: ThisParameterType<T>, ...args: Parameters<T>) {
    const context = this;
    
    return new Promise<ReturnType<T>>((resolve) => {
      if (timer) {
        clearTimeout(timer);
      }
      
      if (immediate && !timer) {
        const result = fn.apply(context, args);
        resolve(result);
      }
      
      timer = setTimeout(() => {
        if (!immediate) {
          const result = fn.apply(context, args);
          resolve(result);
        }
        timer = null;
      }, delay);
      
      resolveFn = resolve;
    });
  } as DebouncedFunction<T>;
  
  debounced.cancel = () => {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    if (resolveFn) {
      // 取消时 reject 可能会更合适,这里简化为 resolve undefined
      resolveFn(undefined as ReturnType<T>);
      resolveFn = null;
    }
  };
  
  return debounced;
}
使用改进版
代码语言:javascript代码运行次数:0运行复制
async function testAdvancedDebounce() {
  const expensiveOperation = (value: number): number => {
    console.log("Computing...", value);
    return value * 2;
  };
  
  const debouncedOp = advancedDebounce(expensiveOperation, 200);
  
  // 快速连续调用
  const [res1, res2, res3] = await Promise.all([
    debouncedOp(1),
    debouncedOp(2),
    debouncedOp(3)
  ]);
  
  console.log(res1, res2, res3); // 6, 6, 6 (所有调用都返回最后一次的结果)
  
  // 取消示例
  const debouncedWithCancel = advancedDebounce(expensiveOperation, 500);
  debouncedWithCancel(10).then(console.log); // 不会执行
  debouncedWithCancel.cancel();
}

与节流(Throttle)的区别

防抖和节流都是控制函数执行频率的技术,但它们有不同的行为:

  • 防抖:在事件停止触发后一段时间执行一次
  • 节流:在一定时间间隔内最多执行一次

根据具体场景选择合适的方案:

  • 防抖适合"最终状态"场景(如搜索建议)
  • 节流适合"过程控制"场景(如滚动事件)

总结

本文介绍了:

  1. 防抖的概念和应用场景
  2. 使用 TypeScript 实现类型安全的防抖函数
  3. 如何扩展防抖函数的功能(取消、返回值处理)
  4. 防抖与节流的区别

通过 TypeScript 的泛型和类型系统,我们可以创建出既安全又灵活的防抖函数,为项目中的高频事件处理提供优雅的解决方案。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-04-28,如有侵权请联系 cloudcommunity@tencent 删除安全函数事件原理typescript