logo

每日前端手写题--day12:深度解析防抖与节流函数的实现与优化

作者:宇宙中心我曹县2025.09.19 12:47浏览量:0

简介:本文聚焦前端开发中的高频面试题——防抖(debounce)与节流(throttle)函数,通过原理剖析、代码实现、性能优化三个维度展开深度解析,帮助开发者掌握这两个核心函数的实现逻辑与实际应用场景。

每日前端手写题—day12:防抖与节流函数的实现与优化

一、防抖与节流的核心价值

在前端开发中,频繁触发的事件(如滚动、输入、窗口调整等)可能导致性能问题。防抖与节流通过控制事件触发频率,有效减少不必要的计算和DOM操作,提升应用性能。例如:

  • 防抖:适用于”最终状态”场景,如搜索框输入(等待用户停止输入后再发起请求)。
  • 节流:适用于”持续触发”场景,如滚动事件监听(固定间隔执行一次)。

二、防抖函数的实现与优化

1. 基础防抖实现

  1. function debounce(func, delay) {
  2. let timer = null;
  3. return function(...args) {
  4. if (timer) clearTimeout(timer);
  5. timer = setTimeout(() => {
  6. func.apply(this, args);
  7. }, delay);
  8. };
  9. }

关键点

  • 使用clearTimeout取消未执行的定时器
  • 通过apply保持原函数的this指向和参数传递
  • 返回一个新函数实现闭包存储timer

2. 立即执行版防抖

  1. function debounce(func, delay, immediate = false) {
  2. let timer = null;
  3. return function(...args) {
  4. if (timer) clearTimeout(timer);
  5. if (immediate && !timer) {
  6. func.apply(this, args);
  7. }
  8. timer = setTimeout(() => {
  9. timer = null;
  10. if (!immediate) {
  11. func.apply(this, args);
  12. }
  13. }, delay);
  14. };
  15. }

优化点

  • 添加immediate参数控制是否立即执行
  • 通过timer = null标记避免重复立即执行

3. 取消功能实现

  1. function debounce(func, delay, immediate = false) {
  2. let timer = null;
  3. const debounced = function(...args) {
  4. // ...原有逻辑...
  5. };
  6. debounced.cancel = function() {
  7. clearTimeout(timer);
  8. timer = null;
  9. };
  10. return debounced;
  11. }

应用场景:组件卸载时取消未执行的防抖函数,避免内存泄漏。

三、节流函数的实现与优化

1. 时间戳版节流

  1. function throttle(func, delay) {
  2. let lastTime = 0;
  3. return function(...args) {
  4. const now = Date.now();
  5. if (now - lastTime >= delay) {
  6. func.apply(this, args);
  7. lastTime = now;
  8. }
  9. };
  10. }

特点:首次立即执行,最后次触发后可能不执行。

2. 定时器版节流

  1. function throttle(func, delay) {
  2. let timer = null;
  3. return function(...args) {
  4. if (!timer) {
  5. timer = setTimeout(() => {
  6. func.apply(this, args);
  7. timer = null;
  8. }, delay);
  9. }
  10. };
  11. }

特点:首次延迟执行,最后次触发后保证执行。

3. 混合版节流(推荐)

  1. function throttle(func, delay) {
  2. let lastTime = 0;
  3. let timer = null;
  4. return function(...args) {
  5. const now = Date.now();
  6. const remaining = delay - (now - lastTime);
  7. if (remaining <= 0) {
  8. if (timer) {
  9. clearTimeout(timer);
  10. timer = null;
  11. }
  12. lastTime = now;
  13. func.apply(this, args);
  14. } else if (!timer) {
  15. timer = setTimeout(() => {
  16. lastTime = Date.now();
  17. timer = null;
  18. func.apply(this, args);
  19. }, remaining);
  20. }
  21. };
  22. }

优势

  • 结合时间戳和定时器特性
  • 保证首次立即执行和最后次触发执行
  • 精确控制执行间隔

四、实际应用场景分析

1. 搜索框防抖

  1. const input = document.querySelector('input');
  2. const searchDebounce = debounce((query) => {
  3. fetch(`/api/search?q=${query}`).then(/* ... */);
  4. }, 500);
  5. input.addEventListener('input', (e) => {
  6. searchDebounce(e.target.value);
  7. });

2. 滚动事件节流

  1. window.addEventListener('scroll', throttle(() => {
  2. console.log('Scroll position:', window.scrollY);
  3. }, 200));

3. 按钮防重复点击

  1. const submitBtn = document.querySelector('#submit');
  2. const submitDebounce = debounce(() => {
  3. // 提交逻辑
  4. }, 1000, true); // 立即执行版
  5. submitBtn.addEventListener('click', submitDebounce);

五、性能优化建议

  1. 参数传递优化:使用...args收集参数,避免直接引用事件对象(如e)可能导致的内存泄漏。
  2. 取消机制:为异步操作添加取消功能,防止组件卸载后继续执行。
  3. 类型检查:添加参数类型校验,提升代码健壮性。
  4. 测试覆盖:编写单元测试验证边界条件(如快速连续触发、延迟期间再次触发等)。

六、常见误区与解决方案

  1. this指向问题:务必使用applycall保持原函数上下文。
  2. 事件对象丢失:防抖/节流函数内部的事件对象是最后一次触发的,如需保留首次对象需特殊处理。
  3. 多次绑定问题:确保不会对同一元素重复绑定防抖/节流函数。

七、进阶思考:结合Promise的防抖

  1. function promiseDebounce(func, delay) {
  2. let timer = null;
  3. let resolveList = [];
  4. return function(...args) {
  5. return new Promise((resolve) => {
  6. resolveList.push(resolve);
  7. if (timer) clearTimeout(timer);
  8. timer = setTimeout(async () => {
  9. const result = await func.apply(this, args);
  10. resolveList.forEach(res => res(result));
  11. resolveList = [];
  12. timer = null;
  13. }, delay);
  14. });
  15. };
  16. }

应用场景:需要获取防抖函数最终结果的异步操作。

总结

防抖与节流是前端性能优化的重要手段,通过今天的手写实现,我们掌握了:

  1. 两种函数的核心实现原理
  2. 不同版本(基础/立即执行/可取消)的实现差异
  3. 实际应用中的最佳实践
  4. 常见问题的解决方案

建议开发者在实际项目中根据场景选择合适版本,并通过性能分析工具验证优化效果。下一期我们将探讨更复杂的前端算法题,敬请期待!

相关文章推荐

发表评论