函数节流(throttle)的实现

2022 年 4 月 17 日 星期日(已编辑)
/
30
这篇文章上次修改于 2024 年 7 月 20 日 星期六,可能部分内容已经不适用,如有疑问可询问作者。

函数节流(throttle)的实现

What is 节流?

为了限制函数一段时间内只能执行一次。 通过使用定时任务,延时方法执行。 在延时的时间内,方法若被触发,则直接退出方法。 从而实现一段时间内只执行一次。

执行过程

  • 当事件触发时,相应的函数不会立即触发,而是会等待一定的时间
  • 当事件密集触发时,函数的触发将会在间隔指定时间后触发一次

    09d59b2ae785070fbe1b8a8fac6e14de

    09d59b2ae785070fbe1b8a8fac6e14de

    应用场景

  • scroll 事件,每隔一秒计算一次位置信息等
  • 浏览器播放事件,每个一秒计算一次进度信息等
  • input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (防抖也可)

实现

简易实现

function throttle(func, interval) {
  //记录上一次开始时间
  let lastTime = 0;
  return function _throttle(...args) {
    //获取当前事件触发的时间
    const nowTime = new Date().getTime();
    //计算还需要多长时间才触发函数
    //给定的时间间隔减去当前时间与上次开始时间之差
    const remainTime = interval - (nowTime - lastTime);
    if (remainTime <= 0) {
      func.apply(args);
      lastTime = nowTime;
    }
  };
}

简易版实现了最基本的节流函数,但是它有个缺陷,在第一次触发节流函数时,他的回调总是会立即执行一次,原因是在计算remainTime时,由于lastTime为0,导致计算结果远小于0。

功能新增(立即执行)

function throttle(
  func,
  interval,
  options = { immediate: true }
) {
  //记录上一次开始时间
  let lastTime = 0;
  const { immediate } = options;
  return function _throttle(...args) {
    //获取当前事件触发的时间
    const nowTime = new Date().getTime();
    //只有当第一次执行的时候会被触发
    if (!lastTime && !immediate) lastTime = nowTime;
    //计算还需要多长时间才触发函数
    //给定的时间间隔减去当前时间与上次开始时间之差
    const remainTime = interval - (nowTime - lastTime);
    if (remainTime <= 0) {
      func.apply(args);
      lastTime = nowTime;
    }
  };
}

功能新增(尾随)

function throttle(
  func,
  interval,
  options = { immediate: true, trailing: true }
) {
  //记录上一次开始时间
  let lastTime = 0;
  let timer = null;
  const { immediate, trailing } = options;
  return function _throttle(...args) {
    //获取当前事件触发的时间
    const nowTime = new Date().getTime();
    if (!lastTime && !immediate) lastTime = nowTime;
    //计算还需要多长时间才触发函数
    //给定的时间间隔减去当前时间与上次开始时间之差
    const remainTime = interval - (nowTime - lastTime);
    if (remainTime <= 0) {
      clearTimeout(timer);
      timer = null;
      func.apply(args);
      lastTime = nowTime;
      return
    }
    if (trailing && !timer) {
      timer = setTimeout(() => {
        timer = null
        func.apply(args);
        //如果是立即执行,那么在间隔内第二次触发时会进入该延时回调
        //例如在第1s时再次触发,那么延时回调会在1s后回调,此时如果用户在2.1s时再进入节流函数,此定时器已经在2s时被执行了,lastTime置0会导致调用两次的bug
        //解决方法:如果是立即执行,那么在触发该回调时将当前时间给lastTime 让上面的函数不执行
        //解决方法:如果不是立即执行,那么将其置0 让上面的函数不执行
        lastTime = !immediate ? 0 : new Date().getTime();
      }, remainTime);
    }
  };
}

功能新增(获取func返回值)

  • 使用Promise
function throttle(
  func,
  interval,
  options = { immediate: true, trailing: true }
) {
  //记录上一次开始时间
  let lastTime = 0;
  let timer = null;
  const { immediate, trailing } = options;
  return function _throttle(...args) {
    return new Promise(function (resolve, reject) {
      //获取当前事件触发的时间
      const nowTime = new Date().getTime();
      if (!lastTime && !immediate) lastTime = nowTime;
      //计算还需要多长时间才触发函数
      //给定的时间间隔减去当前时间与上次开始时间之差
      const remainTime = interval - (nowTime - lastTime);
      if (remainTime <= 0) {
        clearTimeout(timer);
        timer = null;
        const result = func.apply(args);
        resolve(result);
        lastTime = nowTime;
        return;
      }
      if (trailing && !timer) {
        timer = setTimeout(() => {
          timer = null;
          const result = func.apply(args);
          resolve(result);
          lastTime = !immediate ? 0 : new Date().getTime();
        }, remainTime);
      }
    });
  };
}

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...