文章目录
-
- 前言
- 一、先来个生活比喻,秒懂!
- 二、技术概念:两者到底是什么?
-
- [1. 防抖(Debounce)](#1. 防抖(Debounce))
- [2. 节流(Throttle)](#2. 节流(Throttle))
- [3. 核心差异对比表](#3. 核心差异对比表)
- 三、应用场景:两者分别适合做什么?
-
- [1. 防抖的典型场景:](#1. 防抖的典型场景:)
- [2. 节流的典型场景](#2. 节流的典型场景)
- 四、优缺点分析:没有银弹,只有合适的选择
-
- [1. 防抖的优点 ✅](#1. 防抖的优点 ✅)
- [2. 防抖的缺点 ❌](#2. 防抖的缺点 ❌)
- [3. 节流的优点 ✅](#3. 节流的优点 ✅)
- [4. 节流的缺点❌](#4. 节流的缺点❌)
- 五、实战代码:手把手教你实现
-
- [1. 基础版防抖实现](#1. 基础版防抖实现)
- [2. 基础版节流实现](#2. 基础版节流实现)
- [3. 进阶版:带立即执行选项的防抖](#3. 进阶版:带立即执行选项的防抖)
- 六、现代前端中的使用
-
- [1. 在React Hooks中使用](#1. 在React Hooks中使用)
- [2. **使用Lodash库(生产环境推荐)**](#2. 使用Lodash库(生产环境推荐))
- [3. 特殊场景:节流+防抖结合](#3. 特殊场景:节流+防抖结合)
- 总结
前言
作为一名前端开发者,你是否经常为以下场景而感到头疼?
- 页面滚动事件频繁触发,导致页面卡顿
- 搜索框输入时,每输入一个字就发送一次请求
- 按钮被用户疯狂点击,重复提交表单数据
- 窗口缩放时,页面布局计算过于频繁
如果你有以上困扰,那么今天的主角 --- --- 节流(Throttle)与防抖(Debounce),就是你的性能救星!
一、先来个生活比喻,秒懂!
想象一下电梯的运行场景:
防抖(Debounce)就像电梯关门:
当有人进电梯后,电梯不会立即关门,而是等待一段时间(比如10秒)。如果在这10秒内又有新乘客进入,计时器会重置,再等10秒,直到最后一次有人进入后,10秒内没有新乘客,电梯才会真正的关门运行。
节流(Throttle)就像地铁发车:
地铁按照固定时间间隔发车(比如每5分钟一班)。无论这5分钟内来了多少乘客,地铁都准时在5分钟后发车。不会因为突然涌入大量乘客就提前发车,也不会因为乘客少就延迟发车。
二、技术概念:两者到底是什么?
1. 防抖(Debounce)
核心思想:事件被触发后,等待一段时间再执行。如果在等待期间事件又被触发,则重新计时。
简单的说:等你消停了,我再执行
2. 节流(Throttle)
核心思想:在一定时间间隔内,只执行一次事件。
简单的说:再急也得按我的节奏来
3. 核心差异对比表
| 特性 | 防抖(Debounce) | 节流(Throttle) |
|---|---|---|
| 执行时机 | 事件停止触发后执行 | 固定时间间隔执行 |
| 重置机制 | 重新触发后会重置计时 | 触发时不影响既定节奏 |
| 响应速度 | 延迟响应 | 即时响应+后续限制 |
| 类比 | 电梯关门 | 地铁发车 |
三、应用场景:两者分别适合做什么?
1. 防抖的典型场景:
搜索框输入:用户停止输入500ms再发送请求表单验证:输入完成后再进行验证窗口resize:窗口调整完成后再重新计算布局文本编辑器保存:停止编辑后自动保存
2. 节流的典型场景
页面滚动加载:每300ms检查一次滚动位置按钮防止重复点击:点击后2秒内不能再次点击鼠标移动事件:拖拽元素时的位置更新游戏中的按键处理:无论按多快,技能有CD时间
四、优缺点分析:没有银弹,只有合适的选择
1. 防抖的优点 ✅
- 减少不必要的计算:确保只在最后一次操作执行
- 避免重复请求:非常适合搜索、保存等场景
- 节省资源:减少服务器压力和客户端计算
2. 防抖的缺点 ❌
- 即时性差:用户可能觉得"反应慢"
- 不适合连续反馈:如拖拽、游戏控制等需要即时响应的场景
3. 节流的优点 ✅
- 保证即时反馈:第一次触发后立即执行
- 平滑性能曲线:将高频事件转为低频执行
- 用户体验平衡:既响应及时,又不至于卡顿
4. 节流的缺点❌
- 无法保证最后一次执行:时间截止后可能错过最后一次触发
- 可能有延迟感:固定时间可能让用户觉得"不跟手"
五、实战代码:手把手教你实现
1. 基础版防抖实现
javascript
function debounce(func, wait) {
let timeout;
return function() {
const context = this;
const args = arguments;
// 清除之前的计时器
clearTimeout(timeout);
// 设置新的计时器
timeout = setTimeout(function() {
func.apply(context, args);
}, wait);
};
}
// 使用示例
const searchInput = document.getElementById('search');
const searchHandler = () => {
console.log('发送搜索请求...');
};
searchInput.addEventListener('input', debounce(searchHandler, 300));
2. 基础版节流实现
javascript
function throttle(func, wait) {
let lastTime = 0;
return function() {
const context = this;
const args = arguments;
const now = Date.now();
// 如果距离上次执行的时间大于等待时间,则执行
if (now - lastTime >= wait) {
func.apply(context, args);
lastTime = now;
}
};
}
// 使用示例
const scrollHandler = () => {
console.log('检查滚动位置,决定是否加载更多...');
};
window.addEventListener('scroll', throttle(scrollHandler, 200));
3. 进阶版:带立即执行选项的防抖
javascript
function debounceAdvanced(func, wait, immediate = false) {
let timeout;
return function() {
const context = this;
const args = arguments;
const later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
// 使用:第一次立即执行,后续防抖
const saveHandler = () => {
console.log('保存数据...');
};
document.addEventListener('keyup', debounceAdvanced(saveHandler, 1000, true));
六、现代前端中的使用
1. 在React Hooks中使用
javascript
import { useState, useEffect, useCallback } from 'react';
// 自定义防抖Hook
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
// 自定义节流Hook
function useThrottle(value, limit) {
const [throttledValue, setThrottledValue] = useState(value);
const lastRun = useRef(Date.now());
useEffect(() => {
const handler = setTimeout(() => {
if (Date.now() - lastRun.current >= limit) {
setThrottledValue(value);
lastRun.current = Date.now();
}
}, limit - (Date.now() - lastRun.current));
return () => {
clearTimeout(handler);
};
}, [value, limit]);
return throttledValue;
}
2. 使用Lodash库(生产环境推荐)
javascript
import _ from 'lodash';
// Lodash的防抖和节流更完善,推荐生产环境使用
// 防抖
const debouncedSearch = _.debounce(searchAPI, 300);
searchInput.addEventListener('input', debouncedSearch);
// 节流
const throttledScroll = _.throttle(handleScroll, 200);
window.addEventListener('scroll', throttledScroll);
3. 特殊场景:节流+防抖结合
javascript
function throttleDebounce(func, wait, options = {}) {
const { leading = true, trailing = true } = options;
let timeout, lastTime = 0;
return function() {
const context = this;
const args = arguments;
const now = Date.now();
if (!lastTime && !leading) lastTime = now;
const remaining = wait - (now - lastTime);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
lastTime = now;
func.apply(context, args);
} else if (!timeout && trailing) {
timeout = setTimeout(() => {
lastTime = leading ? Date.now() : 0;
timeout = null;
func.apply(context, args);
}, remaining);
}
};
}
总结
| 选择策略 | 选择技术 | 案例 |
|---|---|---|
| 连续操作+需要即时反馈 | 节流 | 页面滚动、鼠标滚动 |
| 连续操作+不需要即时反馈 | 防抖 | 搜索框输入、窗口resize |
| 单次操作+防止重复 | 防抖(立即执行) | 按钮点击、表单提交 |
| 复杂场景 | 节流+防抖(结合) | 实时聊天、协同编辑 |
最后的小建议:
- 80%的场景,基础防抖/节流就够用了
- 生产环境建议使用Lodash等成熟库
- 移动端可以考虑适当延长等待时间(移动端性能更敏感)
- 关键操作(如支付)不要过度节流/防抖,影响用户体验
记住,节流与防抖不是"要不要用"的问题,而是"怎么用得好"的艺术。它们就像前端的"安全带"和"稳定器",平时感觉不到存在,但关键时刻能救你的应用于卡顿崩溃之中!