每日前端手写题--day12:深度解析防抖与节流函数的实现与优化
2025.09.19 12:47浏览量:0简介:本文聚焦前端开发中的高频面试题——防抖(debounce)与节流(throttle)函数,通过原理剖析、代码实现、性能优化三个维度展开深度解析,帮助开发者掌握这两个核心函数的实现逻辑与实际应用场景。
每日前端手写题—day12:防抖与节流函数的实现与优化
一、防抖与节流的核心价值
在前端开发中,频繁触发的事件(如滚动、输入、窗口调整等)可能导致性能问题。防抖与节流通过控制事件触发频率,有效减少不必要的计算和DOM操作,提升应用性能。例如:
- 防抖:适用于”最终状态”场景,如搜索框输入(等待用户停止输入后再发起请求)。
- 节流:适用于”持续触发”场景,如滚动事件监听(固定间隔执行一次)。
二、防抖函数的实现与优化
1. 基础防抖实现
function debounce(func, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
关键点:
- 使用
clearTimeout
取消未执行的定时器 - 通过
apply
保持原函数的this
指向和参数传递 - 返回一个新函数实现闭包存储
timer
2. 立即执行版防抖
function debounce(func, delay, immediate = false) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
if (immediate && !timer) {
func.apply(this, args);
}
timer = setTimeout(() => {
timer = null;
if (!immediate) {
func.apply(this, args);
}
}, delay);
};
}
优化点:
- 添加
immediate
参数控制是否立即执行 - 通过
timer = null
标记避免重复立即执行
3. 取消功能实现
function debounce(func, delay, immediate = false) {
let timer = null;
const debounced = function(...args) {
// ...原有逻辑...
};
debounced.cancel = function() {
clearTimeout(timer);
timer = null;
};
return debounced;
}
应用场景:组件卸载时取消未执行的防抖函数,避免内存泄漏。
三、节流函数的实现与优化
1. 时间戳版节流
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}
特点:首次立即执行,最后次触发后可能不执行。
2. 定时器版节流
function throttle(func, delay) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
}
};
}
特点:首次延迟执行,最后次触发后保证执行。
3. 混合版节流(推荐)
function throttle(func, delay) {
let lastTime = 0;
let timer = null;
return function(...args) {
const now = Date.now();
const remaining = delay - (now - lastTime);
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = now;
func.apply(this, args);
} else if (!timer) {
timer = setTimeout(() => {
lastTime = Date.now();
timer = null;
func.apply(this, args);
}, remaining);
}
};
}
优势:
- 结合时间戳和定时器特性
- 保证首次立即执行和最后次触发执行
- 精确控制执行间隔
四、实际应用场景分析
1. 搜索框防抖
const input = document.querySelector('input');
const searchDebounce = debounce((query) => {
fetch(`/api/search?q=${query}`).then(/* ... */);
}, 500);
input.addEventListener('input', (e) => {
searchDebounce(e.target.value);
});
2. 滚动事件节流
window.addEventListener('scroll', throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 200));
3. 按钮防重复点击
const submitBtn = document.querySelector('#submit');
const submitDebounce = debounce(() => {
// 提交逻辑
}, 1000, true); // 立即执行版
submitBtn.addEventListener('click', submitDebounce);
五、性能优化建议
- 参数传递优化:使用
...args
收集参数,避免直接引用事件对象(如e
)可能导致的内存泄漏。 - 取消机制:为异步操作添加取消功能,防止组件卸载后继续执行。
- 类型检查:添加参数类型校验,提升代码健壮性。
- 测试覆盖:编写单元测试验证边界条件(如快速连续触发、延迟期间再次触发等)。
六、常见误区与解决方案
this
指向问题:务必使用apply
或call
保持原函数上下文。- 事件对象丢失:防抖/节流函数内部的事件对象是最后一次触发的,如需保留首次对象需特殊处理。
- 多次绑定问题:确保不会对同一元素重复绑定防抖/节流函数。
七、进阶思考:结合Promise的防抖
function promiseDebounce(func, delay) {
let timer = null;
let resolveList = [];
return function(...args) {
return new Promise((resolve) => {
resolveList.push(resolve);
if (timer) clearTimeout(timer);
timer = setTimeout(async () => {
const result = await func.apply(this, args);
resolveList.forEach(res => res(result));
resolveList = [];
timer = null;
}, delay);
});
};
}
应用场景:需要获取防抖函数最终结果的异步操作。
总结
防抖与节流是前端性能优化的重要手段,通过今天的手写实现,我们掌握了:
- 两种函数的核心实现原理
- 不同版本(基础/立即执行/可取消)的实现差异
- 实际应用中的最佳实践
- 常见问题的解决方案
建议开发者在实际项目中根据场景选择合适版本,并通过性能分析工具验证优化效果。下一期我们将探讨更复杂的前端算法题,敬请期待!
发表评论
登录后可评论,请前往 登录 或 注册