前言
在前端开发中,某些事件(如 resize、scroll、input、mousemove)会在短时间内频繁触发。如果处理函数涉及 DOM 操作或网络请求,频繁执行会导致页面卡顿或服务器压力过大。防抖和节流正是解决这一问题的两把“手术刀”。
一、 防抖(Debounce)
1. 核心概念
触发事件后 秒内函数只会执行一次。如果 秒内事件再次被触发,则重新计算时间。“等最后一个人说完再行动。”
2. 使用场景
- 搜索框输入:用户连续输入文字,只在停止输入后的 毫秒发送搜索请求。
- 窗口调整:
window.resize时,只在用户停止拖拽后重新计算布局。
3. 实现
function debounce(fn, delay) {
let timer = null;
return function (...args) {
// 如果定时器存在,则清除,重新计时
if (timer) clearTimeout(timer);
// 正常的防抖逻辑
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 测试用例
let count = 0;
function handleInput() {
count++;
console.log('执行次数:', count);
}
const debouncedInput = debounce(handleInput, 1000);
// 模拟快速调用5次
debouncedInput();
debouncedInput();
debouncedInput();
debouncedInput();
debouncedInput();
// 1秒后只会执行一次
setTimeout(() => {
console.log('最终执行次数应该是 1');
}, 1100);
二、 节流(Throttle)
1. 核心概念
连续触发事件,但在 秒内只允许执行一次。节流会显著稀释函数的执行频率。“技能冷却中。”
2. 使用场景
- 鼠标点击:抢购按钮不断点击,规定时间内只发一次请求。
- 滚动监听:页面无限加载时,每隔一段时间请求一次数据,而不是停下才请求。
3. 实现方案对比
方案 A:时间戳版
- 特点:第一次触发立即执行。
function throttleTimestamp(fn, delay) {
let previous = 0;
return function (...args) {
const now = Date.now();
if (now - previous > delay) {
fn.apply(this, args);
previous = now;
}
};
}
// 测试用例
let count = 0;
function handleClick() {
count++;
console.log('执行次数:', count, '时间:', Date.now());
}
const throttledClick = throttleTimestamp(handleClick, 1000);
// 快速调用5次
throttledClick();
throttledClick();
throttledClick();
throttledClick();
throttledClick();
console.log('立即执行次数应该是 1');
// 1.1秒后再调用,应该执行第二次
setTimeout(() => {
throttledClick();
console.log('1.1秒后执行次数应该是 2');
}, 1100);
方案 B:定时器版
- 特点:第一次触发不会立即执行(需等待延迟)
function throttleTimer(fn, delay) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null;
fn.apply(this, args);
}, delay);
}
};
}
// 测试用例
let count = 0;
function handleClick() {
count++;
console.log('执行次数:', count, '时间:', Date.now());
}
const throttledClick = throttleTimer(handleClick, 1000);
// 快速调用5次
throttledClick();
throttledClick();
throttledClick();
throttledClick();
throttledClick();
console.log('立即执行次数应该是 1');
// 1.1秒后再调用,应该执行第二次
setTimeout(() => {
throttledClick();
console.log('1.1秒后执行次数应该是 2');
}, 1100);
三、 防抖与节流的本质区别
为了方便记忆,我们可以通过下表进行对比:
| 特性 | 防抖 (Debounce) | 节流 (Throttle) |
|---|---|---|
| 核心逻辑 | 重置计时器,只认最后一次 | 锁定计时器,在冷却期内忽略触发 |
| 执行频率 | 连续触发时,可能永远不执行(直到停止) | 连续触发时,按固定频率执行 |
| 比喻 | 坐电梯:有人进来门就重新开,直到没人进来才走 | 坐地铁:每隔 10 分钟发一班车,准点出发 |
四、 进阶:如何选择?
- 如果你的需求是 “只需要最终结果” (如输入框验证),选 防抖。
- 如果你的需求是 “过程中的平滑反馈” (如滚动加载、地图缩放),选 节流。