实现防抖和节流
手写 debounce 和 throttle,支持立即执行与取消
问题
实现防抖 (debounce) 和节流 (throttle) 函数,要求:
- 支持立即执行选项
- 支持取消功能
- 正确处理 this 和参数
解答
防抖 (Debounce)
事件触发后等待一段时间才执行,如果期间再次触发则重新计时。
function debounce(fn, wait, immediate = false) {
let timer = null;
const debounced = function (...args) {
// 清除之前的定时器
if (timer) clearTimeout(timer);
if (immediate) {
// 立即执行模式:第一次触发立即执行
const callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) fn.apply(this, args);
} else {
// 延迟执行模式:等待结束后执行
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, wait);
}
};
// 取消功能
debounced.cancel = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
return debounced;
}
节流 (Throttle)
规定时间内只执行一次,无论触发多少次。
function throttle(fn, wait, options = {}) {
let timer = null;
let previous = 0;
// leading: 是否在开始时立即执行
// trailing: 是否在结束后执行一次
const { leading = true, trailing = true } = options;
const throttled = function (...args) {
const now = Date.now();
// 首次调用且不需要立即执行
if (!previous && !leading) {
previous = now;
}
const remaining = wait - (now - previous);
if (remaining <= 0) {
// 时间到了,执行函数
if (timer) {
clearTimeout(timer);
timer = null;
}
previous = now;
fn.apply(this, args);
} else if (!timer && trailing) {
// 设置定时器,保证最后一次触发能执行
timer = setTimeout(() => {
previous = leading ? Date.now() : 0;
timer = null;
fn.apply(this, args);
}, remaining);
}
};
// 取消功能
throttled.cancel = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
previous = 0;
};
return throttled;
}
使用示例
// 防抖:搜索输入
const handleSearch = debounce((keyword) => {
console.log('搜索:', keyword);
}, 300);
input.addEventListener('input', (e) => handleSearch(e.target.value));
// 节流:滚动事件
const handleScroll = throttle(() => {
console.log('滚动位置:', window.scrollY);
}, 200);
window.addEventListener('scroll', handleScroll);
// 取消
handleSearch.cancel();
handleScroll.cancel();
关键点
- 防抖:延迟执行,重复触发会重置计时器;适用于搜索输入、窗口 resize
- 节流:固定频率执行,不会重置计时器;适用于滚动事件、按钮点击
- 立即执行:debounce 的 immediate 参数控制首次是否立即触发
- leading/trailing:throttle 可配置开始和结束时的执行行为
- 取消功能:清除定时器并重置状态,防止组件卸载后执行
目录