❤ 写在前面
如果觉得对你有帮助的话,点个小❤❤ 吧,你的支持是对我最大的鼓励~
个人独立开发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>
性能小贴士与陷阱
✅ 最佳实践
- 选择合适的等待时间:防抖通常300-500ms,节流通常16-200ms(根据60fps计算)
- 考虑使用requestAnimationFrame :对于动画相关的节流,使用
requestAnimationFrame性能更好 - 及时清理:组件卸载时,记得取消定时器
❌ 常见陷阱
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} />;
}
工具库推荐
不想自己实现?这些库已经帮你做好了:
- Lodash :
.debounce()和.throttle()函数功能强大 - Underscore:同样有优秀的实现
- RxJS:响应式编程方式处理事件流
测试你的理解
小测验:
- 用户连续快速调整窗口大小,应该用防抖还是节流?
- 搜索框实时显示搜索结果建议,应该用哪种?
- 射击游戏中角色的射击功能,应该用哪种?
答案:1.防抖(等待调整结束) 2.防抖(等待输入暂停) 3.节流(固定射击频率)
总结
防抖和节流是前端开发中必备的性能优化技巧。记住这个简单的规则:
- 防抖:等待"最后一个"操作完成
- 节流:保持"固定频率"执行
掌握它们不仅能提升应用性能,还能提供更流畅的用户体验。现在,去你的项目中找找看哪些地方可以用上这两个技巧吧!
互动时间:你在项目中用过防抖或节流吗?遇到过什么有趣的问题或挑战?欢迎在评论区分享你的经验!👇