防抖和节流

防抖(debounce)和节流(throttle)都是为了限制事件的频发触发。

防抖是指事件持续触发,但只有等待事件停止触发 n 秒,n 秒后才会执行事件函数,也就是在 n 秒内被重复触发,则重新计时。节流是指持续触发的时候,每 n 秒执行一次函数 ,也就是在n秒内重复触发,只有一次生效。

1. 防抖

原理:事件持续地触发,那么函数就不执行,只有等你停止触发事件 n 秒后,才会执行事件函数

代码:

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

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<button class="btn">点击</button>
<button class="cancle">取消防抖</button>
<div>0</div>
<script>
 let btn = document.querySelector('.btn')
 let canc = document.querySelector('.cancle')
 let div = document.querySelector('div');
 let count = 0

 // immediate 是判断是否立即执行,如果是,那么就是指事件一触发,函数立即执行,然后只有等到停止触发 n 秒后,才可以重新触发执行;否则,就是指事件一触发,然后 n 秒内不再触发事件,函数才执行
 function debounce(fn, wait, immediate) {
   let timer = null;
   let result;  // 作为 fn 的返回值,只在当 immediate 为 true 时才返回
   let that;
   let debounced = function (e) {
     clearTimeout(timer);

     that = this;

     // 事件触发,函数立即执行
     if (immediate) {
       // 这个是判断是否等待停止 n 秒后才触发事件
       let callNow = !timer;
       timer = setTimeout(function () {
         timer = null
       }, wait);

       if (callNow) {
         return result = fn.call(that, e);
       }
     }
     // 事件触发,函数等待 n 秒后执行
     else {
       timer = setTimeout(function () {
         fn.call(that, e)
       }, wait);
     }
   }

   // 当我们进入防抖时间时,调用这个函数就可以退出防抖时间了,就是重置为初始状态
   debounced.cancle = function () {
     clearTimeout(timer);
     timer = null;
   }
   return debounced;
 }


 function fn(e) {
   count++;
   div.innerHTML = count;
 }

 let setUseAction = debounce(fn, 3000, true);
 btn.addEventListener('click', setUseAction);
 canc.addEventListener('click', setUseAction.cancle);

</script>
</body>

</html>

2. 节流

原理:

持续触发事件,那么在这个持续触发事件的时间里,每过 n 秒执行一次函数。但会根据事件首次触发是否执行函数以及事件触发结束后是否执行最后一次函数,效果有所不同,实现的方式也有所不同。
我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。
关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。

代码:

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .container {
      display: table-cell;
      width: 600px;
      height: 300px;
      text-align: center;
      vertical-align: middle;
      background-color: sienna;
    }
  </style>
</head>

<body>
  <div class="container">
    0
  </div>

  <script>
    let container = document.querySelector('.container');
    let count = 0;


    // 1.时间戳实现
    // 会立刻执行,停止触发后不会继续执行事件
    function throttle1(fn, wait) {
      let previous = 0;
      let now;
      let that;
      return function (e) {
        that = this;
        now = +new Date();
        if (now - previous >= wait) {
          fn.call(that, e);
          previous = now;
        }
      }
    }

    // 2.定时器实现
    // 事件会在 n 秒后第一次执行,停止触发后依然会再执行一次事件
    function throttle2(fn, wait) {
      let timer;
      let that;
      return function (e) {
        that = this;
        if (!timer) {
          // fn.call(that, e);   这个是和时间戳一样的效果
          timer = setTimeout(() => {
            fn.call(that, e);
            timer = null;
          }, wait)
        }
      }
    }

    // 3.结合前面两种,实现一个有头有尾的,就是事件触发能立刻执行,停止触发的时候还能再执行一次
    function throttle3(fn, wait) {
      let timer;
      let previous = 0;
      let now;
      let remaining
      let that;

      return function (e) {
        that = this;
        now = +new Date();
        remaining = wait - (now - previous);  // 下次触发 fn 剩余的时间

        // 如果没有剩余的时间了或者改了系统时间   (停止 wait 秒后一点击,就进入这里)
        if (remaining <= 0 || remaining > wait) {
          // 避免定时器里的 fn 和 这里的 fn 同时执行,也就是动画 16ms 执行了两次。
          if (timer) {
            clearTimeout(timer);
            timer = null;
          }
          fn.call(that, e);
          previous = now;
        }
        // 持续点击就进入这里
        else if (!timer) {
          timer = setTimeout(() => {
            fn.call(that, e);
            timer = null;
            previous = +new Date();
          }, remaining)   // 注意,这里是使用 remaining 作为定时器时间
        }
      }
    }

    // 4.设置个 options 作为第三个参数,然后根据传的值判断到底哪种效果,如果
    /*    options = {
         leading: false    // 表示禁用第一次执行(定时器效果,无头有尾)
       }
       options = {
         trailing: false    // 表示禁用停止触发的回调(时间戳效果,有头无尾)
       } */
    // options 不传入参数表示第三种效果,有头头尾

    function throttle4(fn, wait, options) {
      let timer;
      let previous = 0;
      let now;
      let remaining;
      let that;
      if (!options) options = {};

      return function (e) {
        that = this;
        now = +new Date();

        if (options && options.leading === false) previous = now;
        remaining = wait - (now - previous);  // 下次触发 fn 剩余的时间

        if (remaining <= 0 || remaining > wait) {
          if (timer) {
            clearTimeout(timer);
            timer = null;
          }
          fn.call(that, e);
          previous = now;
        }
        else if (!timer && options.trailing !== false) {
          timer = setTimeout(() => {
            fn.call(that, e);
            timer = null;
            previous = +new Date();
          }, remaining)   // 注意,这里是使用 remaining 作为定时器时间
        }
      }
    }

    // 5. 添加暂时退出节流时间
    function throttle5(fn, wait, options) {
      let timer;
      let previous = 0;
      let now;
      let remaining;
      let that;
      if (!options) options = {};

      let throttled = function (e) {
        that = this;
        now = +new Date();

        if (options && options.leading === false) previous = now;
        remaining = wait - (now - previous);  // 下次触发 fn 剩余的时间

        if (remaining <= 0 || remaining > wait) {
          if (timer) {
            clearTimeout(timer);
            timer = null;
          }
          fn.call(that, e);
          previous = now;
        }
        else if (!timer && options.trailing !== false) {
          timer = setTimeout(() => {
            fn.call(that, e);
            timer = null;
            previous = +new Date();
          }, remaining)   // 注意,这里是使用 remaining 作为定时器时间
        }
      }
      // 重置为初始状态
      throttled.cancle = () => {
        clearTimeout(timer);
        previous = 0;
        timer = null;
      }
      return throttled;
    }


    function fn() {
      count++;
      container.innerHTML = count;
    }
    container.addEventListener('mousemove', throttle4(fn, 1000, {
      trailing: false
    }))
  </script>
</body>

</html>
 posted on 2021-03-31 22:32  kly99  阅读(84)  评论(0)    收藏  举报