核心定义
- 防抖 (Debounce): 无论触发多少次,只执行最后一次。本质是**“归零重置”**。
- 节流 (Throttle): 无论触发多少次,按固定频率执行。本质是**“稀释冷却”**。
简单检验 (比喻)
防抖(电梯关门): 哪怕电梯门正在关,只要又进来一个人(新事件触发),电梯门就得重新打开,重新倒计时。直到再也没有人进来了,门才会最终关上。
节流(地铁发车): 即使早高峰站台上挤满了人(高频事件),地铁也只按时刻表(如每5分钟)发一班车。不管这5分钟内来了多少人,车子只按这个节奏走。
机制拆解
A1. 要素 (Components)
- 高频触发源:产生密集信号的源头(如鼠标移动、滚动条滚动、键盘输入)。
- 定时器 (Timer):控制执行时机的核心守门员。
- 执行函数 (Handler):真正需要运行的昂贵逻辑(如API请求、DOM重绘)。
- 状态标识 (Flag/Timestamp):记录上一次执行时间或当前是否有待执行任务。
A2. 结构 (Structure)
-
防抖结构:
- 事件触发 销毁上一个定时器 建立新定时器 等待 秒 执行。
- 规则: 只要在等待期间有新事件,之前的努力全部作废。
-
节流结构:
- 事件触发 检查当前时间是否超过(上一次执行时间 + 间隔)
- 是:执行函数,更新“上一次执行时间”。
- 否:无视该事件(或仅更新参数但不执行)。
- 规则: 只有时间到了通行证才生效,期间的触发被“丢弃”或“合并”。
- 事件触发 检查当前时间是否超过(上一次执行时间 + 间隔)
A3. 系统 (System Function)
当这些零件组合后,该系统实现了**“性能守恒”**:在保证用户体验(反馈)可接受的前提下,将高频的硬件/用户输入信号,转化为低频的程序计算信号,防止CPU过载或API接口被刷爆。
防抖使用情景: 搜索:在用户在输入框输入时,不会立马向后端发起API请求,而是等1000ms后在发起,如果在1000ms内又输入,重置计时器,直到用户停顿超过1000ms后再发起请求。适用于搜索建议、表单验证等场景。
节流使用场景: 快速点赞:当用户快速点击的时候,不会点击一下而直接发起请求,而是等待例如2000ms过后,在统一发起请求记录用户点击的次数,最后清除直接的计时器,在新建一个计时器重新开始计时。适用于滚动加载、按钮防重复提交等
示例代码
/**
* 防抖函数 (Debounce)
* @param {Function} func - 要执行的函数
* @param {number} delay - 延迟时间 (毫秒)
* @returns {Function} 包装后的防抖函数
*/
function debounce(func, delay) {
let timeoutId = null; // 用于存储计时器 ID
// 返回一个新的函数,这个函数才是事件实际绑定的处理函数
return function(...args) {
// 保存函数的执行上下文 (this) 和参数
const context = this;
// 每次触发事件时,都清除上一个计时器
if (timeoutId) {
clearTimeout(timeoutId);
}
// 重新设置一个新的计时器
timeoutId = setTimeout(() => {
// 当计时器时间到了,执行真正的函数
func.apply(context, args);
timeoutId = null; // 执行后清空 ID
}, delay);
};
}
// --- 示例应用 ---
const handleInput = (text) => {
console.log(`发送搜索请求: ${text} (${new Date().toLocaleTimeString()})`);
};
// 创建一个防抖处理函数,延迟 500 毫秒
const debouncedHandleInput = debounce(handleInput, 500);
// 模拟用户快速输入
console.log("--- 防抖开始 ---");
debouncedHandleInput("a"); // 触发 1
debouncedHandleInput("ab"); // 触发 2 (取消 1)
debouncedHandleInput("abc"); // 触发 3 (取消 2)
// 假设用户停止输入 500ms 后... 只有 'abc' 的请求会执行
setTimeout(() => {
console.log("--- 停止触发,等待 500ms 后执行 ---");
}, 700);节流:
/**
* 节流函数 (Throttle) - 时间戳实现
* @param {Function} func - 要执行的函数
* @param {number} delay - 限制的间隔时间 (毫秒)
* @returns {Function} 包装后的节流函数
*/
function throttle(func, delay) {
let lastTime = 0; // 上次执行的时间
return function(...args) {
const context = this;
const now = Date.now(); // 当前时间
// 如果距离上次执行的时间间隔大于或等于设定的延迟时间
if (now - lastTime >= delay) {
// 执行真正的函数
func.apply(context, args);
// 更新上次执行的时间
lastTime = now;
}
// 否则,忽略本次调用
};
}
// --- 示例应用 ---
const handleScroll = () => {
console.log(`处理滚动事件... (${new Date().toLocaleTimeString()})`);
};
// 创建一个节流处理函数,限制每 1000 毫秒(1秒)最多执行一次
const throttledHandleScroll = throttle(handleScroll, 1000);
// 模拟用户在 1500 毫秒内连续触发
console.log("--- 节流开始 ---");
throttledHandleScroll(); // 0ms: 立即执行 (lastTime = 0)
throttledHandleScroll(); // 100ms: 忽略 (时间未到 1000ms)
throttledHandleScroll(); // 500ms: 忽略
throttledHandleScroll(); // 999ms: 忽略
// 假设用户在 1001ms 时再次触发
setTimeout(() => {
throttledHandleScroll(); // 1001ms: 执行 (因为 now - lastTime > 1000)
throttledHandleScroll(); // 1100ms: 忽略
}, 1001);