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

❤ 写在前面

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

个人独立开发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.节流(固定射击频率)

总结

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

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

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


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

相关推荐
尾善爱看海1 小时前
不常用的浏览器 API —— Web Speech
前端
美酒没故事°1 小时前
vue3拖拽+粘贴的综合上传器
前端·javascript·typescript
jingling5553 小时前
css进阶 | 实现罐子中的水流搅拌效果
前端·css
悟能不能悟4 小时前
前端上载文件时,上载多个文件,但是一个一个调用接口,怎么实现
前端
可问春风_ren5 小时前
前端文件上传详细解析
前端·ecmascript·reactjs·js
羊小猪~~5 小时前
【QT】--文件操作
前端·数据库·c++·后端·qt·qt6.3
晚风资源组6 小时前
CSS文字和图片在容器内垂直居中的简单方法
前端·css·css3
Miketutu7 小时前
Flutter学习 - 组件通信与网络请求Dio
开发语言·前端·javascript
光影少年8 小时前
前端如何调用gpu渲染,提升gpu渲染
前端·aigc·web·ai编程
Surplusx9 小时前
运用VS Code前端开发工具完成网页头部导航栏
前端·html