防抖和节流-防抖鸿蒙版本实现

一、防抖(Debounce)VS节流 (Throttle)

(一)防抖 (Debounce)及示例

核心思想 :在事件被触发后,等待 n 秒后再执行函数。如果在这 n 秒内事件又被触发,则重新计时,等待新的 n 秒过后再执行。

通俗比喻回城技能

在玩MOBA游戏(如英雄联盟、王者荣耀)时,你按下回城键(触发事件),需要等待8秒才能成功回城(执行函数)。如果在8秒内你被攻击或者移动了(再次触发事件),回城会被打断,你必须重新按下回城键并再次等待8秒。

效果 :将多次频繁的操作合并为一次,只执行最后一次。

实现原理

使用 setTimeout 来延迟执行。每次触发事件时,都清除之前的定时器,并设置一个新的定时器。

适用场景

  1. 搜索框输入建议(Search Suggest):用户连续输入时,只在用户停止输入一段时间后才向服务器发送请求,而不是每输入一个字符就请求一次。

  2. 窗口大小调整(Resize):等待用户调整浏览器窗口结束后,再执行计算布局的函数,避免在调整过程中频繁计算。

  3. 文本编辑器自动保存:在用户停止编辑一段时间后,自动保存内容。

代码示例

javascript 复制代码
function debounce(func, wait) {
  let timeout;
  return function() {
    // 清除上一次的定时器
    clearTimeout(timeout);
    // 设置新的定时器
    timeout = setTimeout(() => {
      func.apply(this, arguments);
    }, wait);
  };
}

// 使用示例
const myInput = document.getElementById('search');
const doAjax = () => { console.log('发送Ajax请求...'); };

myInput.addEventListener('input', debounce(doAjax, 500));

(二)节流 (Throttle)及示例

核心思想 :在一个单位时间内,无论事件被触发多少次,都只执行一次函数。

通俗比喻技能冷却

英雄的技能有冷却时间(CD)。你按下技能键(触发事件),技能会立即释放(执行函数),然后进入CD。在CD期间,无论你按多少次技能键,技能都不会再次释放,直到CD结束。

效果 :控制函数执行的频率,使其以一个固定的、较低的频率执行。

实现原理

可以通过时间戳或定时器来实现。常见的是使用一个标志位(或者时间差)来判断是否应该执行函数。

适用场景

  1. 滚动加载(Infinite Scroll) :在用户滚动页面时,每隔一定时间检查一次滚动位置,看是否接近底部,而不是在每次 scroll 事件都检查。

  2. 鼠标移动(Mousemove):比如实现一个拖拽功能,不需要获取每一个像素的移动坐标,只需每隔一段时间获取一次即可。

  3. 按钮点击(防止重复提交):防止用户在短时间内多次点击提交按钮。

代码示例(使用时间戳版本)

javascript 复制代码
function throttle(func, wait) {
  let lastTime = 0;
  return function() {
    const now = Date.now();
    // 如果距离上次执行的时间超过了等待时间,则执行函数
    if (now - lastTime >= wait) {
      func.apply(this, arguments);
      lastTime = now; // 更新最后一次执行的时间
    }
  };
}

// 使用示例
const myButton = document.getElementById('submit');
const handleClick = () => { console.log('提交表单...'); };

myButton.addEventListener('click', throttle(handleClick, 2000)); // 2秒内最多执行一次

(三)核心区别总结(表格对比)

特性 防抖 (Debounce) 节流 (Throttle)
核心思想 延迟执行,只执行最后一次 间隔执行,固定频率执行
执行时机 在连续触发结束后,等待一段时间执行 在连续触发过程中,按照固定时间间隔执行
是否重置 每次新触发都会重置计时器 新触发不会重置计时,按固定节奏执行
类比 回城技能(不停打断,只回一次) 技能冷却(CD好了就放,放完进入CD)
适用场景 搜索建议、Resize 滚动加载、拖拽、频繁点击

如何选择?

  • 当你关心最终状态 时,使用防抖。例如,搜索框只关心用户最后输入了什么。

  • 当你需要在过程中以可控的节奏处理事件 时,使用节流。例如,滚动时你需要持续地判断是否到了页面底部。

++简单来说:防抖是"最后一个人说了算",节流是"按规矩办事,一段时间内只办一次"。++

(四)防抖:节流使用≈7:3

为什么感觉"节流不怎么用"?

您的观察是正确的,在现代前端开发中,防抖的使用场景确实比节流更加普遍和直观。主要原因如下:

1. UI/UX 层面的替代方案更加优雅

就像您提到的按钮防重复点击的场景:

  • 节流方案:用节流函数包装点击处理函数,设置2秒内只能点击一次

  • 更优方案 :点击后立即显示 loading 状态(禁用按钮+加载动画)

    • 用户体验更好(明确知道状态)

    • 代码更直观易维护

    • 能更好地处理异步操作的成功/失败状态

javascript 复制代码
// 节流方案(不够优雅)
const handleSubmit = throttle(() => {
  // 提交逻辑
}, 2000);

// 更优方案(推荐)
const handleSubmit = async () => {
  setLoading(true);
  button.disabled = true;
  
  try {
    await submitForm();
    // 成功处理
  } catch (error) {
    // 错误处理
  } finally {
    setLoading(false);
    button.disabled = false;
  }
};
2. 防抖有不可替代的独特场景

输入框搜索建议这个场景,防抖几乎是唯一的最佳选择:

  • 用户连续输入时,我们不关心中间过程,只关心最终输入的内容

  • 如果使用节流,仍然会在输入过程中频繁请求,达不到优化目的

  • 防抖能完美地"等待用户完成输入"

javascript 复制代码
// 防抖在输入框中的应用(无可替代)
const searchInput = document.getElementById('search');
const fetchSuggestions = debounce(async (query) => {
  const results = await api.getSuggestions(query);
  renderSuggestions(results);
}, 300);

searchInput.addEventListener('input', (e) => {
  fetchSuggestions(e.target.value);
});
3. 节流的传统场景被新技术替代

一些传统的节流使用场景现在有了更好的解决方案:

  • 滚动加载 :现在更多使用 Intersection Observer API,性能更好且更精确

  • Resize 监听:同样可以用 Resize Observer API 替代

但节流仍有其价值场景

虽然使用频率不如防抖,但节流在以下场景中仍然很重要:

1. 实时性要求较高的交互
javascript 复制代码
// 拖拽元素 - 需要实时反馈,但不能过于频繁
const dragElement = document.getElementById('draggable');
const updatePosition = throttle((x, y) => {
  element.style.left = x + 'px';
  element.style.top = y + 'px';
}, 16); // ~60fps

dragElement.addEventListener('mousemove', (e) => {
  updatePosition(e.clientX, e.clientY);
});
2. 频繁的事件监听
javascript 复制代码
// 窗口滚动时记录位置 - 用于数据分析
const trackScroll = throttle(() => {
  analytics.track('scroll_position', window.scrollY);
}, 1000); // 每秒最多记录一次

window.addEventListener('scroll', trackScroll);
3. 游戏或动画相关

在游戏开发中,节流常用于限制某些操作的频率,比如技能冷却、射击间隔等。

二、防抖实现-鸿蒙

类实现

TypeScript 复制代码
class Debouncer {
  private delay: number = 300
  private timer: number = 0

  constructor(delay: number = 300) {
    this.delay = delay;
  }

  debounce(func: Function) {
    // 返回一个新的函数
    return (...args: ESObject[]) => {
      // 清除之前的定时器
      if (this.timer) {
        clearTimeout(this.timer);
      }
      // 设置新的定时器
      this.timer = setTimeout(() => {
        // 使用闭包访问 func 和 args
        func(...args);
      }, this.delay);
    };
  }
}


export {
  Debouncer
}


// 使用
const debouncer: Debouncer = new Debouncer(300); // 防抖函数
  Row() {
          ForEach(this.downMenuList, (item: ItemModel2) => {
            Row() {
              Text(item.label)
                .fontSize(16)
                .fontColor('#333')
              Image($r('app.media.icon_arrow_b'))
                .width(10)
                .rotate({
                  angle: this.iconRotate[item.value - 1]
                })
                .margin({ left: 5 })
            }
            .width('50%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .onClick(debouncer.debounce(() => {
              this.handleDownMenu(item)
            }))
          })
//

闭包版本的防抖实现

TypeScript 复制代码
function createDebouncer(delay: number = 300) {
  let timer: number = 0;  // 闭包变量 - 替代 this.timer
  
  // 返回防抖函数
  return (func: Function) => {
    // 返回实际执行的函数
    return (...args: any[]) => {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(() => {
        func(...args);
      }, delay);  // 使用闭包中的 delay
    };
  };
}

// 使用方式
const debouncer = createDebouncer(500);
const debouncedFunc = debouncer((plate: string) => {
  console.log('校验车牌:', plate);
});

// 调用
debouncedFunc('粤B12345');

闭包:

相关推荐
不一样的少年_3 小时前
老板问我:AI真能一键画广州旅游路线图?我用 MCP 现场开图
前端·人工智能·后端
东方石匠3 小时前
Javascript常见面试题
前端·javascript·面试
恋猫de小郭3 小时前
Flutter 也有类 React Flow 的节点流程编辑器,快来了解下刚刚开源的 vyuh_node_flow
android·前端·flutter
性野喜悲3 小时前
<script setup lang=“ts“>+uniapp实现轮播(swiper)效果
前端·javascript·vue.js·小程序·uni-app
wangdaoyin20103 小时前
UniApp 在手机端(Android)打开选择文件和文件写入
android·前端·uni-app
用户497357337984 小时前
高端Web全栈工程师精品就业实战班课程 从零打造Web架构师
前端
我们没有完整的家4 小时前
技术速递|Playwright MCP 调试 Web 应用时,GitHub Copilot 生成断言脚本的实用方法
前端·github·copilot
universe_014 小时前
前端学习之八股和算法
前端·学习
一川_4 小时前
ElementUI分页器page-size切换策略:从保持当前页到智能计算的优化实践
前端