普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月2日首页

2026年,你敢信一些知名开源库都还不会正确使用防抖节流吗

作者 Electrolux
2026年4月2日 15:02

摘要:防抖(debounce)和节流(throttle)是前端开发中高频使用的性能优化技巧,也是八股文的经典。但许多开发者甚至知名开源库的维护者都在误用它们。本文通过分析 vben 和 vue-office 两个热门项目的真实 PR,揭示常见的使用误区,并给出最佳实践。


Leader 大群 @ 我:"CSV 预览在 Mac 触控板上滑得太快了"

2026.3.16,那是一个普通的工作日,我正在专注地敲代码,突然 Leader 在公司大群里 @ 我:

image.png

"CSV 文件预览在 Mac 下触控板左右移动的速度好快,谁能调整一下?"

我心里一紧,我们的项目,用的是 vue-office 组件库预览office,我打开源码 debug了一下——居然是 throttle 的用法有问题。组件库里每次滚动事件都创建一个新的 throttle 函数,然后立即执行,根本没有节流效果。触控板的高频滚动事件直接穿透了,导致表格左右飞快移动。

项目中patch后,直接提了 PR。

这件事也让我想起了一年前给 vue-vben-admin 提的另一个类似 PR——那次是远程搜索的 debounce 用错了,也是每次输入都创建新实例

  • vue-vben-admin:一个拥有 32K+ Star 的现代化 Vue3 管理后台
  • vue-office:一个拥有 6K+ Star 的 Office 文件预览组件库

vben的项目维护者直接回复 "nice catch",

image.png

让我不禁思考:防抖和节流看似简单,但连这些知名库的资深开发者都会踩坑,说明这背后一定有什么容易忽视的细节。

本文就来复盘这两个案例,聊聊这些常见的使用误区。


vue-vben-admin 的远程搜索

问题案例

<template>
  <Input @search="useDebounceFn(onSearch, 300)" />
</template>

问题分析

核心错误@search 每次被调用时,都创建了一个新的 debounce 函数实例,然后立即执行它。

这意味着:

  1. 用户输入 "h",调用 debounceOptionsFn,创建 debounce A,立即执行 A,发起请求
  2. 用户继续输入 "he",再次调用 debounceOptionsFn,创建 debounce B,立即执行 B,再次发起请求
  3. 用户输入 "hel",再次调用,创建 debounce C,立即执行 C,又发起请求...

每个 debounce 实例都是全新的,内部的定时器逻辑完全没有机会发挥作用——防抖函数永远不会被触发(指延迟后的触发),而是每次都被立即执行。


vue-office 的 Excel 滚动优化

问题案例

vue-office 在处理 Excel 表格滚动时,想要使用 throttle 来优化性能,但代码存在类似的问题:

// ❌ 错误:在事件监听中直接使用 throttle
if (/Firefox/i.test(window.navigator.userAgent)) throttle(moveY(evt.detail), 50);
if (temp === tempX) throttle(moveX(deltaX), 50);
if (temp === tempY) throttle(moveY(deltaY), 50);

问题分析

这个错误的模式与上面的 debounce 案例如出一辙:

  1. 每次滚动事件触发,都创建一个新的 throttle 函数
  2. 新创建的 throttle 函数立即执行,没有任何节流效果

更严重的是,如果这是一个高频滚动场景,不断创建新的 throttle 函数还会带来内存泄漏的风险。


框架中的正确使用方式

Vue 组合式 API

<script setup>
import { debounce } from 'lodash-es'
import { onUnmounted } from 'vue'

// 在组件级别创建,保持引用稳定
const debouncedSearch = debounce(async (query) => {
  const results = await api.search(query)
  items.value = results
}, 300)

// 绑定到事件
function onInput(value) {
  debouncedSearch(value)
}

// 组件卸载时清理
onUnmounted(() => {
  debouncedSearch.cancel()
})
</script>

React Hooks

import { useMemo, useEffect } from 'react'
import { debounce } from 'lodash-es'

function SearchComponent() {
  // ✅ 使用 useMemo 保持 debounce 函数引用稳定
  const debouncedSearch = useMemo(
    () => debounce((query) => {
      api.search(query)
    }, 300),
    [] // 空依赖,只在组件挂载时创建
  )

  // 组件卸载时清理
  useEffect(() => {
    return () => {
      debouncedSearch.cancel()
    }
  }, [debouncedSearch])

  return (
    <input onChange={(e) => debouncedSearch(e.target.value)} />
  )
}

原生 JavaScript

import { throttle } from 'lodash-es'

class ScrollHandler {
  constructor() {
    // 在构造函数中创建,确保引用稳定
    this.throttledScroll = this.handleScroll.bind(this)
    this.throttledScroll = throttle(this.throttledScroll, 100)
    
    window.addEventListener('scroll', this.throttledScroll)
  }

  handleScroll() {
    // 处理滚动逻辑
  }

  destroy() {
    window.removeEventListener('scroll', this.throttledScroll)
    this.throttledScroll.cancel()
  }
}

结语

防抖和节流看起来简单,但实际使用中却暗藏陷阱。我在审查 vue-vben-admin 和 vue-office 代码时的经历告诉我:即使是经验丰富的开发者和知名开源项目,也可能在这些"基础"概念上栽跟头。

从那以后,我在代码审查时会多问一句写代码时多想一想函数引用,也养成了检查 debounce/throttle 使用模式的习惯。希望本文能帮助你写出更健壮、性能更优的代码。

如果你在项目中发现了类似的防抖节流误用,或者有其他最佳实践想分享,欢迎交流讨论!


参考资源


这篇文章记录了我发现并修复两个知名开源项目防抖节流问题的经历。如果你也遇到过类似的坑,欢迎在评论区聊聊你的故事。

❌
❌