防抖动,节流js函数详解

防抖动,节流js函数详解,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>防抖动,节流</title>
</head>
<body>
  <button id="debounce">防抖动</button>
  <button id="throttle">节流</button>
  <script>
    //对于按钮防点击来说的实现:如果函数是立即执行的,就立即调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。
    //一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。一旦你点累了,定时器时间到,定时器重置为 null,就可以再次点击了。
    /**
     * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
     *
     * @param  {function} func        回调函数
     * @param  {number}   wait        表示时间窗口的间隔
     * @param  {boolean}  immediate   设置为ture时,是否立即调用函数
     * @return {function}             返回客户调用函数
     */
    function debounce(func, wait = 50, immediate = true) {
      let timer , context , args;
      //延迟 执行函数
      const later = () => setTimeout(() => {
        //延迟函数执行,清空缓存的定时器序号
        timer = null;
        //不是立即执行,即等到延迟时间后执行, 使用到之前缓存的参数和上下文
        if (!immediate) {
          func.apply(context, args);
          context = args = null;
        }
      }, wait);
      //这里返回的函数是每次实际调用的函数
      return function (...params) {
        //如果没有创建延迟函数(later),则创建
        console.log(timer);
        if (!timer) {
          timer = later();
          //如果是立即执行,则调用函数
          //否则缓存参数和上下文
          if (immediate) {
            func.apply(this, params);
          }
          else {
            context = this;
            args = params;
          }
        }
        //如果已有延迟函数(later),则清除原来的定时器,重新成生一个, 这样做延迟函数会重新计时
        else {
          clearTimeout(timer);
          timer = later();
        }
      };
    }

    document.getElementById('debounce').onclick = debounce(function (){
      console.log('防抖动');
    }, 1000);
    // 这个是用来获取当前时间戳的
    function now() {
      return +new Date()
    }
    //节流
    //防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
    /**
       * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
       *
       * @param  {function}   func      回调函数
       * @param  {number}     wait      表示时间窗口的间隔
       * @param  {object}     options   如果想忽略开始函数的的调用,传入{leading: false}。
       *                                如果想忽略结尾函数的调用,传入{trailing: false}
       *                                两者不能共存,否则函数不能执行
       * @return {function}             返回客户调用函数
    */
    function throttle(func, wait, options) {
      var context, args, result;
      var timeout = null;
      // 之前的时间戳
      var previous = 0;
      // 如果 options 没传则设为空对象
      if (!options) options = {};
      // 定时器回调函数
      var later = function() {
        // 如果设置了 leading,就将 previous 设为 0
        // 用于下面函数的第一个 if 判断
        previous = options.leading === false ? 0 : now();
        // 置空一是为了防止内存泄漏,二是为了下面的定时器判断
        timeout = null;
        result = func.apply(context, args);
        console.log('result', result);
        if (!timeout) context = args = null;
      };
      return function() {
        // 获得当前时间戳
        var now_ = now();
        // 首次进入前者肯定为 true
      // 如果需要第一次不执行函数
      // 就将上次时间戳设为当前的
        // 这样在接下来计算 remaining 的值时会大于0
        if (!previous && options.leading === false) previous = now_;
        // 计算剩余时间
        var remaining = wait - (now_ - previous);
        console.log('now_', now_);
        console.log('previous', previous);
        console.log('wait', wait);
        console.log('remaining------------------', remaining);
        context = this;
        args = arguments;
        // 如果当前调用已经大于上次调用时间 + wait
        // 或者用户手动调了时间
      // 如果设置了 trailing,只会进入这个条件
      // 如果没有设置 leading,那么第一次会进入这个条件
      // 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
      // 其实还是会进入的,因为定时器的延时
      // 并不是准确的时间,很可能你设置了2秒
      // 但是他需要2.2秒才触发,这时候就会进入这个条件
        if (remaining <= 0 || remaining > wait) {
          // 如果存在定时器就清理掉否则会调用二次回调
          if (timeout) {
            clearTimeout(timeout);
            timeout = null;
          }
          previous = now_;
          result = func.apply(context, args);
          if (!timeout) context = args = null;
          console.log('--------------------------remaining');
        } else if (!timeout && options.trailing !== false) {
          // 判断是否设置了定时器和 trailing
          // 没有的话就开启一个定时器
          // 并且不能不能同时设置 leading 和 trailing
          console.log('setTimeout+++++++++');
          timeout = setTimeout(later, remaining);
        }
        return result;
      };
    };
    document.getElementById('throttle').onclick = throttle(function (){
      console.log('节流');
      return 'd';
    }, 2000);
  </script>
</body>
</html>

发布者

cylyiou

cylyiou

分享前端知识

支付宝扫码打赏微信打赏

如果文章对您有帮助,不妨移至上方按钮小额赞助我一下,让我有动力继续写出高质量的教程。

发表评论

电子邮件地址不会被公开。