实现节流函数

97 阅读1分钟

一、节流介绍

节流:按照设定的频率触发回调函数,可以减少一段时间内时间的触发频率。

应用

  1. 滚动事件: 当用户滚动页面时,触发的滚动事件可能会非常频繁。使用节流可以确保滚动事件处理函数不会过于频繁地执行,从而减轻浏览器的负担。
  2. 调整窗口大小: 当用户调整浏览器窗口大小时,窗口大小事件会频繁触发。通过节流,可以确保在一定时间内只执行一次窗口大小调整事件的处理函数。

二、功能实现

2.1基本功能

首先需要设定一个触发频率Interval

设定一个开始时间startTime和一个触发事件的时间nowTime

计算出距离触发函数的时间waitTime=interval - (nowTime - startTime)

startTime初始化为0,当第一次触发事件时,nowTime会是一个时间戳,例如1692608776,waitTime得到的结果会是一个很大的负数,所以第一次会发生调用。

调用后startTime赋值为触发的时间nowTime,所以第二次触发时,如果距离要触发的时间大于0,则不会再次触发。

function mythrotttle(fn,interval) {
        let startTime = 0;
        let timer = null;

        const _throttle = function () {
          const nowTime = new Date().getTime();

          const waitTime = interval - (nowTime - startTime); //关键一步,计算出距离触发函数的时间
          if (waitTime < 0) { 
            //this的绑定和传递参数
            fn.apply(this, arguments);
            startTime = nowTime;
          }
        };

        return _throttle;
      }

2.2禁用首次执行

由于startTime初始值为0,所以会造成第一次的waitTime是小于0的,所以只需要在首次触发时将startTime=nowTime,两者相减会变为0,导致结果waitTime大于0,不会触发回调。

function mythrotttle(fn,interval,immediate=true) {
        let startTime = 0;
        let timer = null;

        const _throttle = function () {
          const nowTime = new Date().getTime();
          
      //第一次执行由于starTime=0,nowTime很大,导致减出来的值是一个负值
          if (!immediate && startTime === 0) {
            startTime = nowTime;
          }

          const waitTime = interval - (nowTime - startTime); //关键一步,计算出距离触发函数的时间
          if (waitTime < 0) { 
            //this的绑定和传递参数
            fn.apply(this, arguments);
            startTime = nowTime;
          }
        };

        return _throttle;
      }

2.3尾调用

尾调用可以实现在最后一次会触发函数

尾调用处理过程:当在startTime=10s时(这里用时间错也是一样的道理),也就是10s时发生过一次回调,当用户在15s点击之后,此时距离触发回调事件=waiTime=5s,还没到触发时间,在这时候会开启一个5s的定时器,会在20s时触发。当用户之后都没有点击操作时,时间一到20s,回调就会立即触发。

为什么触发函数时需要if(timer) clearTimeout(timer); 清除定时器?

原因:举一个例子,当第一次回调触发在10s,11s点击之后开启定时器,如果在20s时触发点击,此时之前开启的定时器也会触发,为了避免触发两次,需要清除。

 function mythrotttle(
        fn,
        interval,
        { immediate = true, trailing = false } = {}
      ) {
        let startTime = 0;
        let timer = null;

        const _throttle = function () {
          const nowTime = new Date().getTime();
          
          if (!immediate && startTime === 0) {
            startTime = nowTime;
          }

          const waitTime = interval - (nowTime - startTime); 
          if (waitTime < 0) {
            //如果触发的话,需要清除之前的定时器,防止触发两次
            if (timer) clearTimeout(timer); 
            fn.apply(this, arguments);
            startTime = nowTime;
            timer = null;
            return; //如果发生调用的话,直接返回
          }

          //是否尾调用,开启过一次定时器就不需要开启第二次了
          if (trailing && !timer) {
            timer = setTimeout(() => {
              fn.apply(this, arguments);
              startTime = new Date().getTime(); //这里要获取触发时的时间作为最新开始时间,
              timer = null;
            }, waitTime); //这里是waitTime,上次调用过后,从这个调用时间开始计算,距离触发周期调用的时间(不用都是interval时间),因为要周期性触发
          }
        };

        return _throttle;
      }