🎯 一、为什么要掌握防抖与节流?
它们是前端性能优化的“老三样”之一,常用于:
- **防止频繁触发:**输入框搜索、窗口 resize、滚动监听
- 面试必问场景题:“你怎么优化滚动事件监听?”
🧠 二、概念区别一张图看懂
| 特性 | 防抖(debounce) | 节流(throttle) |
|---|
| 执行时机 | 停止一段时间后执行一次 | 固定时间间隔内最多执行一次 |
| 场景 | 搜索框输入、按钮防重复提交 | 页面滚动、resize、mousemove |
✍️ 三、手写 debounce(防抖)
✅ 实现 1:基础版(触发后延迟执行)
function debounce(fn, delay = 300) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
✅ 示例使用
window.addEventListener('resize', debounce(() => {
console.log('窗口变化');
}, 500));
✅ 实现 2:立即执行版(leading = true)
function debounce(fn, delay = 300, immediate = false) {
let timer = null;
return function (...args) {
const isFirst = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (!immediate) fn.apply(this, args);
}, delay);
if (isFirst) fn.apply(this, args);
};
}
✍️ 四、手写 throttle(节流)
✅ 实现 1:时间戳版(立即执行)
function throttle(fn, wait = 300) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= wait) {
lastTime = now;
fn.apply(this, args);
}
};
}
✅ 实现 2:定时器版(延迟执行)
function throttle(fn, wait = 300) {
let timer = null;
return function (...args) {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, wait);
};
}
✅ 实现 3:混合版(可配置 leading 和 trailing)
function throttle(fn, wait = 300, { leading = true, trailing = true } = {}) {
let timer = null;
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (!leading && lastTime === 0) {
lastTime = now;
}
const remaining = wait - (now - lastTime);
if (remaining <= 0) {
clearTimeout(timer);
timer = null;
lastTime = now;
fn.apply(this, args);
} else if (!timer && trailing) {
timer = setTimeout(() => {
lastTime = leading ? Date.now() : 0;
timer = null;
fn.apply(this, args);
}, remaining);
}
};
}
✅ 五、验证用例(滚动监听)
window.addEventListener('scroll', throttle(() => {
console.log('页面滚动');
}, 1000));
❗ 六、常见面试陷阱
| 问题 | 答案 |
|---|
| 防抖能否立即执行? | 可以,加 immediate 参数 |
| 节流 trailing 和 leading 如何实现? | 控制时间戳/定时器配合 |
| 节流和防抖能否合并? | 理论上不建议,但可以写工具库支持模式切换 |
this 和 arguments 会丢失吗? | 是的,需用 apply(this, args) 绑定上下文 |
📘 七、延伸思考(面试加分)
lodash.debounce 和 lodash.throttle 内部实现有何异同?
- 如何封装一个 Vue/React 通用指令或 Hook 实现?