返回

从lodash源代码解读debounce和throttle的奥秘

见解分享

导言

lodash库中的debounce和throttle函数是处理用户输入时的重要工具。它们通过延迟函数执行或限制执行频率来优化用户体验和应用性能。本文将深入剖析这两个函数的源代码,揭示其内部机制,并探讨其应用场景和差异。

Debounce:延迟执行

debounce函数接受一个函数和一个延迟时间(以毫秒为单位)作为参数。当函数被调用时,debounce会创建一个计时器。如果在延迟时间内没有再次调用该函数,则计时器到期后会执行该函数。这种机制可以有效防止函数被频繁调用,从而提高性能并避免不必要的操作。

const debounced = _.debounce(() => {
  // 此函数在延迟时间后执行
}, 500);

// 连续调用 debounced 函数
debounced();
debounced();
debounced();

// 500ms后,debounced 函数只执行一次

Throttle:限制执行频率

与debounce不同,throttle函数限制函数在指定时间间隔内只能执行一次。当函数被调用时,throttle会创建一个计时器,如果在计时器到期之前再次调用该函数,则计时器会重置。这种机制可以确保函数在指定时间间隔内只执行一次,从而限制函数的执行频率。

const throttled = _.throttle(() => {
  // 此函数在指定时间间隔内只执行一次
}, 500);

// 连续调用 throttled 函数
throttled();
throttled();
throttled();

// 500ms后,throttled 函数只执行一次

源代码分析

Debounce

const debounce = (func, wait, options = {}) => {
  let lastArgs, lastThis, maxWait, result, timerId, lastCallTime;

  // 确保 wait 是一个正数
  wait = Number(wait) || 0;
  if (!options.leading) {
    maxWait = wait;
  } else if (options.trailing) {
    maxWait = wait * 2;
  } else {
    maxWait = wait;
  }

  function delayed() {
    const remaining = wait - (new Date().getTime() - lastCallTime);
    if (remaining <= 0) {
      if (timerId) {
        clearTimeout(timerId);
      }
      lastArgs = lastThis = timerId = undefined;
      result = func.apply(lastThis, lastArgs);
      if (!timerId) {
        lastCallTime = new Date().getTime();
      }
    } else {
      timerId = setTimeout(delayed, remaining);
    }
  }

  function cancel() {
    if (timerId) {
      clearTimeout(timerId);
    }
    lastArgs = lastThis = timerId = undefined;
  }

  return function debounced(...args) {
    const currentThis = this;
    const now = new Date().getTime();
    lastArgs = args;
    lastThis = currentThis;
    lastCallTime = now;

    // 对于 leading 为 true 的情况
    if (!timerId && options.leading) {
      result = func.apply(currentThis, args);
      timerId = setTimeout(delayed, wait);
    }

    // 对于 trailing 为 true 的情况
    if (timerId) {
      clearTimeout(timerId);
      timerId = setTimeout(delayed, maxWait - (now - lastCallTime));
    }

    // 对于 leading 和 trailing 均为 false 的情况
    else {
      if (now - lastCallTime > wait) {
        timerId = setTimeout(delayed, wait);
      }
    }

    return result;
  };
};

Throttle

const throttle = (func, wait, options = {}) => {
  let leading = true,
    trailing = true;

  if (typeof options.leading === 'boolean') {
    leading = !!options.leading;
  }
  if (typeof options.trailing === 'boolean') {
    trailing = !!options.trailing;
  }

  return debounce(func, wait, {
    leading,
    trailing,
    maxWait: wait
  });
};

应用场景

Debounce

  • 输入框自动完成:延迟搜索请求,直到用户停止输入。
  • 窗口大小改变监听器:延迟调整布局,直到窗口大小稳定。
  • 滚动事件处理程序:延迟加载数据,直到滚动停止。

Throttle

  • 按钮点击处理程序:限制按钮在特定时间间隔内的点击次数。
  • 鼠标移动事件处理程序:限制鼠标移动事件的触发频率。
  • 动画帧请求:限制动画帧的更新频率,以提高性能。

差异

特征 Debounce Throttle
执行时机 延迟 限制频率
触发方式 连续调用后延迟执行 指定时间间隔后执行
参数 函数、延迟时间、选项 函数、延迟时间、选项
应用场景 避免频繁调用 控制执行频率

总结

debounce和throttle函数是优化用户输入处理的宝贵工具。通过深入理解其工作原理和应用场景,开发者可以更有效地利用它们来提高应用性能和用户体验。lodash的实现提供了完善的选项,使开发者能够根据具体需求定制函数的行为。