防抖和节流

防抖和节流

防抖

在准备执行某项操作时,预设一个间隔时间,然后计时,达到预设的时间间隔之后才执行逻辑;
在没到达间隔时间这段时间内,如果有多次重复操作,那么后面的操作会顶替掉前面的计时任务,重新开始计时。

应用场景: 输入框输入内容过程中去服务器查询数据,不能每次input都去查询,所以只有当用户输入停顿一定时间间隔时,认为用户希望获得查询结果。

代码如下:

/**
 * 防抖: 重复操作重置定时器
 */
function debounce (callback, delay, immediate) {
  let timer, context, args, isExecuted;

  let run = () => {
    timer = setTimeout(() => {
      !isExecuted && callback.apply(context, args);
      clearTimeout(timer);
      timer = null;
    }, delay)
  }

  return function() {
    context = this;
    args = arguments;
    // 重复进入时,设置为false,用于 run内部进行判断
    // 若没有此标志位,run内只能用 immediate 来判断优化
    // 但问题是,如果使用immediate,那么定时器完成后始终不会执行callback
    isExecuted = false;

    // 定时器存在时,清空并重建
    // 没有必要释放timer,因为run方法会重新赋值
    if (timer) {
      clearTimeout(timer);
      run()
    } else {
      // timer不存在的两种情况
      // 1. 初始状态,还没有创建任何定时器
      // 2. 完成了一次执行,timer被释放
      // 所以当timer不存在时,需要创建;
      // timer存在时,说明还在进行计时,此时需要清除定时器并重新创建
      !!immediate && callback.apply(context, args);
      isExecuted = true; // 标记为已执行
      run();
    }
  }
}

测试代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>节流Throttle</title>
  <style>
    #container {
      width: 200px;
      height: 200px;
      text-align: center;
      line-height: 200px;
      background-color: gray;
      color: white;
    }
  </style>
</head>
<body>
  <input id="input" type="text">
  <span>input输入结果</span>
  <script>
    window.onload = () => {
      var inputEl = document.getElementById('input');

      inputEl.addEventListener('input', debounce(function(e) {
        this.nextElementSibling.textContent = this.value
      }, 1000, true));
    }

    /**
     * 防抖: 重复操作重置定时器
     */
    function debounce (callback, delay, immediate) {
      let timer, context, args, isExecuted;

      let run = () => {
        timer = setTimeout(() => {
          !isExecuted && callback.apply(context, args);
          clearTimeout(timer);
          timer = null;
        }, delay)
      }

      return function() {
        context = this;
        args = arguments;
        // 重复进入时,设置为false,用于 run内部进行判断
        // 若没有此标志位,run内只能用 immediate 来判断优化
        // 但问题是,如果使用immediate,那么定时器完成后始终不会执行callback
        isExecuted = false;

        // 定时器存在时,清空并重建
        // 没有必要释放timer,因为run方法会重新赋值
        if (timer) {
          clearTimeout(timer);
          run()
        } else {
          // timer不存在的两种情况
          // 1. 初始状态,还没有创建任何定时器
          // 2. 完成了一次执行,timer被释放
          // 所以当timer不存在时,需要创建;
          // timer存在时,说明还在进行计时,此时需要清除定时器并重新创建
          !!immediate && callback.apply(context, args);
          isExecuted = true; // 标记为已执行
          run();
        }
      }
    }

  </script>
</body>
</html>

节流

当短时间内重复执行某项操作时,予以忽略,只执行一次;知道执行完成之后才会重新添加执行能力。

实现原理: 维护一个定时器,每次执行操作是都判断定时器是否存在,如果定时器存在,直接return;
如果定时器不存在,则创建定时器,定时器到期后执行,并清除定时器和定时器标志

代码如下:

const throttle = (callback, delay, immediate) => {
  let timer, context, args;

  let run = () => {
    timer = setTimeout(() => {
      // 仅在不是立即模式时执行,防止二次执行
      // 当 immediate 为 true时, run方法的作用仅用于创建定时器,用于下次执行控制
      if (!immediate) callback.apply(context, args)

      clearTimeout(timer) // 清除定时器,
      timer = null // 回收timer,防止对后面的执行造成影响
    }, delay)
  }

  return function() {
    // 存储上下文和实参列表
    context = this;
    args = arguments;

    // 当前如果有定时器任务,则取消操作
    if (timer) return

    // 如果需要立即执行,则执行
    if (immediate) callback.apply(context, arguments)
    // 再次执行run方法创建定时器用于下次判断
    run()
  }
}

测试:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>节流Throttle</title>
  <style>
    #container {
      width: 200px;
      height: 200px;
      text-align: center;
      line-height: 200px;
      background-color: gray;
      color: white;
    }
  </style>
</head>
<body>
  <div id="container"></div>
  <script>
    window.onload = () => {
      var containerEl = document.getElementById('container');
      var i = 0;
      containerEl.addEventListener('mousemove', throttle((e) => {
        containerEl.textContent = i++
      }, 200, true));
    }

    /**
     * 节流
     */
    const throttle = (callback, delay, immediate) => {
      let timer, context, args;

      let run = () => {
        timer = setTimeout(() => {
          // 仅在不是立即模式时执行,防止二次执行
          // 当 immediate 为 true时, run方法的作用仅用于创建定时器,用于下次执行控制
          !immediate && callback.apply(context, args);
          clearTimeout(timer) // 清除定时器,
          timer = null // 回收timer,防止对后面的执行造成影响
        }, delay)
      }

      return function() {
        // 存储上下文和实参列表
        context = this;
        args = arguments;
        
        if (timer) return; // 当前如果有定时器任务,则取消操作

        !!immediate && callback.apply(context, args); // 如果需要立即执行,则执行
        run() // 再次执行run方法创建定时器用于下次判断
      }
    }
  </script>
</body>
</html>

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>节流Throttle</title>
  <style>
    #container {
      width: 200px;
      height: 200px;
      text-align: center;
      line-height: 200px;
      background-color: gray;
      color: white;
      margin-top: 20px;
    }
  </style>
</head>
<body>
  <input id="input" type="text">
  <span>input输入结果</span>
  <div id="container"></div>
  <script>
    window.onload = () => {
      var inputEl = document.getElementById('input');
      var containerEl = document.getElementById('container');
      var i = 0;

      inputEl.addEventListener('input', debounce(function(e) {
        this.nextElementSibling.textContent = this.value
      }, 1000, true));

      containerEl.addEventListener('mousemove', throttle((e) => {
        containerEl.textContent = i++
      }, 200, true));
    }

    /**
     * 防抖: 重复操作重置定时器
     */
    function debounce (callback, delay, immediate) {
      let timer, context, args, isExecuted;

      let run = () => {
        timer = setTimeout(() => {
          !isExecuted && callback.apply(context, args);
          clearTimeout(timer);
          timer = null;
        }, delay)
      }

      return function() {
        context = this;
        args = arguments;
        // 重复进入时,设置为false,用于 run内部进行判断
        // 若没有此标志位,run内只能用 immediate 来判断优化
        // 但问题是,如果使用immediate,那么定时器完成后始终不会执行callback
        isExecuted = false;

        // 定时器存在时,清空并重建
        // 没有必要释放timer,因为run方法会重新赋值
        if (timer) {
          clearTimeout(timer);
          run()
        } else {
          // timer不存在的两种情况
          // 1. 初始状态,还没有创建任何定时器
          // 2. 完成了一次执行,timer被释放
          // 所以当timer不存在时,需要创建;
          // timer存在时,说明还在进行计时,此时需要清除定时器并重新创建
          !!immediate && callback.apply(context, args);
          isExecuted = true; // 标记为已执行
          run();
        }
      }
    }

    /**
     * 节流
     */
    const throttle = (callback, delay, immediate) => {
      let timer, context, args;

      let run = () => {
        timer = setTimeout(() => {
          // 仅在不是立即模式时执行,防止二次执行
          // 当 immediate 为 true时, run方法的作用仅用于创建定时器,用于下次执行控制
          !immediate && callback.apply(context, args);
          clearTimeout(timer) // 清除定时器,
          timer = null // 回收timer,防止对后面的执行造成影响
        }, delay)
      }

      return function() {
        // 存储上下文和实参列表
        context = this;
        args = arguments;
        
        if (timer) return; // 当前如果有定时器任务,则取消操作

        !!immediate && callback.apply(context, args); // 如果需要立即执行,则执行
        run() // 再次执行run方法创建定时器用于下次判断
      }
    }
  </script>
</body>
</html>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 本篇课题,或许早已是烂大街的解读文章。不过春招系列面试下来,不少伙伴们还是似懂非懂地栽倒在(~面试官~)深意的笑容...
    以乐之名阅读 1,816评论 0 9
  • 函数防抖(debounce) 防抖函数 debounce 指的是某个函数在某段时间内,无论触发了多少次回调,都只执...
    lacduang阅读 225评论 0 0
  • 在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负...
    iqing2012阅读 819评论 0 1
  • 作为一名前端开发者,我们经常会处理各种事件,比如常见的click、scroll、 resize等等。仔细一想,会发...
    一米阳光kk阅读 520评论 0 0
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,590评论 16 22