防抖与节流:前端性能优化“双剑客”

❤ 写在前面

如果觉得对你有帮助的话,点个小❤❤ 吧,你的支持是对我最大的鼓励~

个人独立开发wx小程序,感谢支持!


引言:为什么需要它们?

想象一下,你在搜索框中每输入一个字母,页面就疯狂地向服务器发送请求;或者你疯狂地滚动页面,触发了无数次的滚动事件处理函数...这不仅会让用户体验变差,还可能让服务器崩溃!这时,防抖和节流就登场了------它们是控制函数执行频率的两位"超级英雄"。

生活比喻:更容易理解的概念

📦 防抖 (Debounce) --- "电梯关门原理"

想象你在等电梯:当第一个人进入电梯后,电梯门开始关闭(计时开始)。如果在这期间又有人进来,电梯会重新开始关门计时。只有当一段时间内没有人再进入,电梯门才会真正关闭并开始运行。

核心思想:事件触发后,等待一段时间再执行,如果在这期间再次触发,则重新计时。

💧 节流 (Throttle) --- "水龙头滴水原理"

想象一个调节过的水龙头:无论你怎么拧开关,水都以固定的频率一滴一滴地流出,不会因为你的快速操作而改变流速。

核心思想:在一定时间内,无论事件触发多少次,只执行一次。

技术对比:一目了然的区别

需要等待最后一次操作
需要固定间隔执行




事件触发
使用哪种策略?
防抖 Debounce
节流 Throttle
设置等待计时器
在等待期间

有新事件触发?
执行目标函数
判断是否在冷却期
执行函数并进入冷却
忽略此次触发
冷却结束后重置状态

核心区别对比表

特性 防抖 (Debounce) 节流 (Throttle)
执行时机 最后一次触发后等待一段时间执行 固定时间间隔执行
重新触发的影响 重新计时 不影响固定间隔
类比 电梯关门 水龙头滴水
适用场景 搜索框输入、窗口调整 滚动事件、按钮点击

实战代码:手把手实现

基础防抖实现

javascript 复制代码
function debounce(func, wait) {
  let timeout;
  
  return function(...args) {
    // 清除之前的计时器
    clearTimeout(timeout);
    
    // 设置新的计时器
    timeout = setTimeout(() => {
      func.apply(this, args);
    }, wait);
  };
}

// 使用示例:搜索框输入
const searchInput = document.getElementById('search');
const searchFunc = () => {
  console.log('发送搜索请求:', searchInput.value);
};

searchInput.addEventListener('input', debounce(searchFunc, 500));

基础节流实现

javascript 复制代码
function throttle(func, wait) {
  let lastTime = 0;
  let timeout;
  
  return function(...args) {
    const now = Date.now();
    const remaining = wait - (now - lastTime);
    
    if (remaining <= 0) {
      // 已经超过等待时间,立即执行
      lastTime = now;
      func.apply(this, args);
    }
  };
}

// 使用示例:滚动加载更多
window.addEventListener('scroll', throttle(() => {
  console.log('检查是否需要加载更多内容...');
}, 200));

进阶版本:功能更全面

带立即执行选项的防抖

javascript 复制代码
function debounceAdvanced(func, wait, immediate = false) {
  let timeout;
  
  return function(...args) {
    const context = this;
    
    const later = () => {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    
    const callNow = immediate && !timeout;
    
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    
    if (callNow) {
      func.apply(context, args);
    }
  };
}

带取消功能的节流

javascript 复制代码
function throttleAdvanced(func, wait) {
  let lastTime = 0;
  let timeout;
  
  const throttled = function(...args) {
    const context = this;
    const now = Date.now();
    const remaining = wait - (now - lastTime);
    
    if (remaining <= 0) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      lastTime = now;
      func.apply(context, args);
    } else if (!timeout) {
      timeout = setTimeout(() => {
        lastTime = Date.now();
        timeout = null;
        func.apply(context, args);
      }, remaining);
    }
  };
  
  throttled.cancel = () => {
    clearTimeout(timeout);
    timeout = null;
  };
  
  return throttled;
}

真实场景应用

🛒 场景一:电商搜索框(防抖)

javascript 复制代码
// 用户输入时,只在停止输入500ms后发送请求
const searchProduct = debounce((keyword) => {
  fetch(`/api/search?q=${keyword}`)
    .then(response => response.json())
    .then(products => {
      // 更新搜索结果
      updateSearchResults(products);
    });
}, 500);

searchInput.addEventListener('input', (e) => {
  searchProduct(e.target.value);
});

🖱️ 场景二:防止按钮重复点击(节流)

javascript 复制代码
// 用户疯狂点击提交按钮,但每2秒只处理一次
const submitOrder = throttle(() => {
  console.log('提交订单请求发送...');
  // 发送订单请求
}, 2000);

submitButton.addEventListener('click', submitOrder);

🎮 场景三:游戏射击(节流)

javascript 复制代码
// 游戏角色开枪,无论玩家多快点击,枪都有冷却时间
const shoot = throttle(() => {
  console.log('发射子弹!');
  createBullet();
  playShootSound();
}, 300); // 每300ms只能发射一次

fireButton.addEventListener('click', shoot);

📱 场景四:无限滚动加载(节流)

javascript 复制代码
// 滚动时检查位置,但不要太频繁
const checkScrollPosition = throttle(() => {
  const scrollTop = window.pageYOffset;
  const windowHeight = window.innerHeight;
  const documentHeight = document.documentElement.scrollHeight;
  
  // 距离底部100px时加载更多
  if (documentHeight - (scrollTop + windowHeight) < 100) {
    loadMoreContent();
  }
}, 200);

window.addEventListener('scroll', checkScrollPosition);

现代前端框架中的使用

React Hooks 版本

javascript 复制代码
import { useCallback, useRef } from 'react';

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

// 节流Hook
function useThrottle(callback, delay) {
  const lastRun = useRef(0);
  
  return useCallback((...args) => {
    const now = Date.now();
    
    if (now - lastRun.current >= delay) {
      callback(...args);
      lastRun.current = now;
    }
  }, [callback, delay]);
}

// 在组件中使用
function SearchComponent() {
  const [query, setQuery] = useState('');
  
  const handleSearch = useDebounce((searchTerm) => {
    // 发送搜索请求
    fetchResults(searchTerm);
  }, 500);
  
  return (
    <input
      value={query}
      onChange={(e) => {
        setQuery(e.target.value);
        handleSearch(e.target.value);
      }}
    />
  );
}

Vue 3 版本

vue 复制代码
<template>
  <input v-model="searchText" @input="handleSearch" />
</template>

<script setup>
import { ref } from 'vue';
import { debounce } from 'lodash-es'; // 或使用自定义实现

const searchText = ref('');
const searchResults = ref([]);

const handleSearch = debounce(async () => {
  const response = await fetch(`/api/search?q=${searchText.value}`);
  searchResults.value = await response.json();
}, 500);
</script>

性能小贴士与陷阱

✅ 最佳实践

  1. 选择合适的等待时间:防抖通常300-500ms,节流通常16-200ms(根据60fps计算)
  2. 考虑使用requestAnimationFrame :对于动画相关的节流,使用requestAnimationFrame性能更好
  3. 及时清理:组件卸载时,记得取消定时器

❌ 常见陷阱

javascript 复制代码
// 错误:每次渲染都创建新的防抖函数
function Component() {
  // 这样每次渲染都会创建新的debounce实例
  const handleInput = debounce(() => {
    // ...
  }, 500);
  
  return <input onChange={handleInput} />;
}

// 正确:使用useRef或useCallback保持引用
function Component() {
  const handleInputRef = useRef(
    debounce(() => {
      // ...
    }, 500)
  );
  
  return <input onChange={handleInputRef.current} />;
}

工具库推荐

不想自己实现?这些库已经帮你做好了:

  1. Lodash.debounce().throttle() 函数功能强大
  2. Underscore:同样有优秀的实现
  3. RxJS:响应式编程方式处理事件流

测试你的理解

小测验:

  1. 用户连续快速调整窗口大小,应该用防抖还是节流?
  2. 搜索框实时显示搜索结果建议,应该用哪种?
  3. 射击游戏中角色的射击功能,应该用哪种?

答案:1.防抖(等待调整结束) 2.防抖(等待输入暂停) 3.节流(固定射击频率)

总结

防抖和节流是前端开发中必备的性能优化技巧。记住这个简单的规则:

  • 防抖:等待"最后一个"操作完成
  • 节流:保持"固定频率"执行

掌握它们不仅能提升应用性能,还能提供更流畅的用户体验。现在,去你的项目中找找看哪些地方可以用上这两个技巧吧!


互动时间:你在项目中用过防抖或节流吗?遇到过什么有趣的问题或挑战?欢迎在评论区分享你的经验!👇

相关推荐
KLW752 小时前
vue v-if和v-show比较
前端·css·css3
梵尔纳多2 小时前
使用 Electron 实现一个简单的文本编辑器
前端·javascript·electron
晴殇i2 小时前
SPA首屏加载速度优化!
前端
qq. 28040339842 小时前
react 副作用探究
前端·react.js
小oo呆2 小时前
【自然语言处理与大模型】LangChainV1.0入门指南:核心组件Streaming
前端·javascript·easyui
Aotman_2 小时前
Vue.directive:自定义指令及传参
前端·javascript·vue.js·elementui·ecmascript·es6
wangchen_02 小时前
C++<fstream> 深度解析:文件 I/O 全指南
开发语言·前端·c++
程序员码歌2 小时前
短思考第266天,玩IP路上的几点感悟,这几点很重要!
前端·后端·创业
梵得儿SHI2 小时前
2025 Vue 技术实战全景:从工程化到性能优化的 8 个落地突破
前端·javascript·vue.js·pinia2.2·响应式数据分片·展望vue3.6·2025年vue技术栈