JS-手写系列:防抖与节流

0 阅读3分钟

前言

在前端开发中,某些事件(如 resizescrollinputmousemove)会在短时间内频繁触发。如果处理函数涉及 DOM 操作或网络请求,频繁执行会导致页面卡顿或服务器压力过大。防抖节流正是解决这一问题的两把“手术刀”。


一、 防抖(Debounce)

1. 核心概念

触发事件后 nn 秒内函数只会执行一次。如果 nn 秒内事件再次被触发,则重新计算时间。“等最后一个人说完再行动。”

2. 使用场景

  • 搜索框输入:用户连续输入文字,只在停止输入后的 nn 毫秒发送搜索请求。
  • 窗口调整window.resize 时,只在用户停止拖拽后重新计算布局。

3. 实现

  function debounce(fn, delay) {
    let timer = null;

    return function (...args) {
      // 如果定时器存在,则清除,重新计时
      if (timer) clearTimeout(timer);

      // 正常的防抖逻辑
      timer = setTimeout(() => {
        fn.apply(this, args);
      }, delay);
    };
  }

  // 测试用例
  let count = 0;
  function handleInput() {
    count++;
    console.log('执行次数:', count);
  }

  const debouncedInput = debounce(handleInput, 1000);

  // 模拟快速调用5次
  debouncedInput();
  debouncedInput();
  debouncedInput();
  debouncedInput();
  debouncedInput();

  // 1秒后只会执行一次
  setTimeout(() => {
    console.log('最终执行次数应该是 1');
  }, 1100);

二、 节流(Throttle)

1. 核心概念

连续触发事件,但在 nn 秒内只允许执行一次。节流会显著稀释函数的执行频率。“技能冷却中。”

2. 使用场景

  • 鼠标点击:抢购按钮不断点击,规定时间内只发一次请求。
  • 滚动监听:页面无限加载时,每隔一段时间请求一次数据,而不是停下才请求。

3. 实现方案对比

方案 A:时间戳版

  • 特点:第一次触发立即执行。
 function throttleTimestamp(fn, delay) {
    let previous = 0;
    return function (...args) {
      const now = Date.now();
      if (now - previous > delay) {
        fn.apply(this, args);
        previous = now;
      }
    };
  }

  // 测试用例
  let count = 0;
  function handleClick() {
    count++;
    console.log('执行次数:', count, '时间:', Date.now());
  }

  const throttledClick = throttleTimestamp(handleClick, 1000);

  // 快速调用5次
  throttledClick();
  throttledClick();
  throttledClick();
  throttledClick();
  throttledClick();

  console.log('立即执行次数应该是 1');

  // 1.1秒后再调用,应该执行第二次
  setTimeout(() => {
    throttledClick();
    console.log('1.1秒后执行次数应该是 2');
  }, 1100);

方案 B:定时器版

  • 特点:第一次触发不会立即执行(需等待延迟)
  function throttleTimer(fn, delay) {
    let timer = null;
    return function (...args) {
      if (!timer) {
        timer = setTimeout(() => {
          timer = null;
          fn.apply(this, args);
        }, delay);
      }
    };
  }

  // 测试用例
  let count = 0;
  function handleClick() {
    count++;
    console.log('执行次数:', count, '时间:', Date.now());
  }

  const throttledClick = throttleTimer(handleClick, 1000);

  // 快速调用5次
  throttledClick();
  throttledClick();
  throttledClick();
  throttledClick();
  throttledClick();

  console.log('立即执行次数应该是 1');

  // 1.1秒后再调用,应该执行第二次
  setTimeout(() => {
    throttledClick();
    console.log('1.1秒后执行次数应该是 2');
  }, 1100);

三、 防抖与节流的本质区别

为了方便记忆,我们可以通过下表进行对比:

特性防抖 (Debounce)节流 (Throttle)
核心逻辑重置计时器,只认最后一次锁定计时器,在冷却期内忽略触发
执行频率连续触发时,可能永远不执行(直到停止)连续触发时,按固定频率执行
比喻坐电梯:有人进来门就重新开,直到没人进来才走坐地铁:每隔 10 分钟发一班车,准点出发

四、 进阶:如何选择?

  • 如果你的需求是 “只需要最终结果” (如输入框验证),选 防抖
  • 如果你的需求是 “过程中的平滑反馈” (如滚动加载、地图缩放),选 节流