underscore之节流函数

392 阅读3分钟

在上一篇文章中我写了防抖函数,现在来写节流函数啦,因为二者功能相似,使用场景也有重合,所以两篇文章连着写,但二者的区别是什么呢?

简单理解:

防抖函数会在事件刚刚执行的那个点或者执行结束后等待一段时间触发我们绑定的函数,而节流函数就见名知意了,在事件不断触发时,绑定函数也在不断执行,不过执行的频率被限制了,因此可以说成,在事件被一直触发的时期每隔一段时间执行一次绑定函数。

理解了二者的区别后,那又要怎么实现呢,我们来先来温习一下防抖函数怎么实现的吧,通过setTimeout来实现,每次调用函数时,先利用clearTimeout来解除之前的定时器,然后重新绑定一个定时器,而节流函数能这样做吗?不管了,我们先来试试!!!(建议查看我前一篇防抖函数后进行后面的阅读,不过没看上一篇文章,关系也不大,应该不难理解,但如果没看懂,一定是这个原因)

1. setTimeout

先来看一个案例,监听鼠标移动事件,html代码如下

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>xhx</title>
  <style>
    .box {
      width: 300px;
      height: 300px;
      background-color: green;
      font-size: 40px;
      text-align: center;
    }
  </style>
</head>

<body>
  <div class="box" id="box">

  </div>
</body>
<script>
  let box = document.getElementById('box');
  let count = 0;
  const MouseMove = () => {
    console.log("send a request message");
    console.log("consume cpu");
    count++;
    box.innerHTML = count;
  }
  box.addEventListener("mousemove", MouseMove)
</script>

</html>

现在来对鼠标移动事件进行节流,下面会在鼠标事件不断触发的同时每隔1000ms执行一次绑定的函数

// 第一版代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>xhx</title>
  <style>
    .box {
      width: 300px;
      height: 300px;
      background-color: green;
      font-size: 40px;
      text-align: center;
    }
  </style>
</head>

<body>
  <div class="box" id="box">

  </div>
</body>
<script>
// 节流函数
  function throttle(func, wait){
    var timeout;
    return function(){
      const _this = this;
      const args = arguments;
      if(!timeout){
        timeout = setTimeout(function(){
          func.apply(_this, args);
          timeout = null;
        }, wait)
      }
    }
  }

  let box = document.getElementById('box');
  let count = 0;
  const MouseMove = () => {
    console.log("send a request message");
    console.log("consume cpu");
    count++;
    box.innerHTML = count;
  }
  box.addEventListener("mousemove", throttle(MouseMove, 1000))
</script>

</html>

上面都不难理解,我们来看一下动画演示吧

现在我们已经用setTimeout的方式完成了节流操作,迈出了第一步,已经成功一半了,现在我们来试试一种更简单的方式,利用时间戳来实现节流

2. 时间戳

在上面的代码中,只需要改变throttle就可以了,效果图和上面有点差别,这个一进入就会触发绑定函数,最后鼠标移出时,也就是结束时不会触发函数,而用setTimeout实现的会在在鼠标移动事件结束时再触发一次

// 第二版
  function throttle(func, wait){
    let nowTime;
    let lastTime = 0;
    return function(){
      const _this = this;
      const args = arguments;
      nowTime = +new Date(); // +new Date() 相当于new Date().getTime(),获取现在的时间戳
      if(nowTime - lastTime > wait){ // 如果距离上次事件超过了一个wait时间则触发函数
        func.apply(_this, args)
        lastTime = nowTime // 把现在的时间设为下一次的上次时间
      }
    }
  }

到目前为止,我们已经用两种方法实现了节流功能了,但其中存在一些问题,也是我们上面提到的二者的差别,setTimeout在停止触发后还能执行一次,而时间戳能在刚开始触发的时候执行一次,现在我们能不能实现既能开始时触发,又能结束后触发一次的函数呢?所以有了我们的第三版函数

3. setTimeout + 时间戳

这里会有一点点的难,相当于我来说,但是我会尽量描述的详细点

// 第三版
<script>
  // 节流函数
  function throttle(func, wait) {
    let nowTime;     // 现在的时间
    let lastTime = 0; // 上次的时间
    let timeout;      // 定时器
    let remainTime = 0;// 距离下次调用绑定函数剩余的时间

    return function () {
      const _this = this;
      const args = arguments;
      nowTime = +new Date(); // nowTime永远为现在的时间戳
      remainTime = wait - (nowTime - lastTime);
      // remainTime > wait是为了防止系统时间改变,因为+new Date()取的是系统时间,不影响大局读者可自行删去"|| remainTime > wait"
      if (remainTime <= 0 || remainTime > wait) { // remainTime <= 0代表着已经过了一个wait,事件刚触发就执行靠这个判断
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        func.apply(_this, args);
        lastTime = nowTime;
        timeout = null;
      } else if (!timeout) { // 事件结束后再一次执行靠这个判断
        timeout = setTimeout(() => {
          func.apply(_this, args);
          lastTime = nowTime;
          timeout = null;
        }, remainTime);
      }
    }
  }

  let box = document.getElementById('box');
  let count = 0;
  const MouseMove = () => {
    console.log("send a request message");
    console.log("consume cpu");
    count++;
    box.innerHTML = count;
  }
  box.addEventListener("mousemove", throttle(MouseMove, 1000))
</script>

效果动画如下图

4. 更多

underscore中的节流函数还可以传入option选项,对传的值判断到底哪种效果,我们约定:

leading:false 表示禁用第一次执行

trailing: false 表示禁用停止触发的回调

但在这里就不深究了,无疑是将代码逐渐优化,而作为一个学习者需学习任何东西都有时间成本,现在我们已经搞清楚其中的原理了,应该为读到这里的自己点一个赞,为孩子写到这里也点一个赞吧!!!

其实我们还可以有一个取消功能,但我在上篇文章防抖函数中已经写了,这里就不赘述了

如果这篇文章对你有用,请点赞鼓励孩子一下吧!

这里由衷感谢JavaScript专题之跟着 underscore 学节流文章,以及系列文章