564 函数的防抖和节流

  • 函数的防抖和节流
  • 防抖:在用户频繁触发的时候,只识别一次(识别第一次/识别最后一次)
  • 节流: 目的是频繁触发中缩减频率。假设我们频率设置为500ms,我们频繁触发了10000ms,对于防抖,则只触发一次;对于节流,则触发20次

// 第一次点击,没有立即执行,等待500MS,看看这个时间内有没有触发第二次,有触发第二次,说明在频繁点击,不去执行我们的事情(继续看第三次和第二次间隔...);如果没有触发第二次,则认为非频繁点击,此时去触发;

/*

  • debounce:实现函数的防抖(目的是频繁触发中只执行一次)
  • @params
  • func:需要执行的函数
  • wait:检测防抖的间隔频率
  • immediate:是否是立即执行(如果为true是控制第一次触发的时候就执行函数,默认FALSE是以最后一次触发为准)
  • @return
  • 可被调用执行的函数
    */

/*
防抖:我的注释
1、在指定时间内,触发第二次,清除上一次的操作(定时器),重新计时,生成新的操作;触发第三次,清除上一次的操作,重新计时,生成新的操作,以此类推。超过指定时间,则执行操作。
总之,频繁操作,即在指定时间内,操作2次及2次以上,就清空上一次的操作,重新计时,生成新的操作。
2、防抖,不是每间隔指定时间,都执行一次,而是指定一个间隔时间,在间隔时间内,有木有触发2次,只要触发2次,就是频繁的,就以第一次或最后一次为主
*/


防抖的应用场景

  • 输入框中频繁的输入内容,搜索或者提交信息;
  • 频繁的点击按钮,触发某个事件;
  • 监听浏览器滚动事件,完成某些特定操作;
  • 用户缩放浏览器的resize事件;

总之,密集的事件触发,我们只希望触发比较靠后发生的事件,就可以使用防抖函数。


节流的应用场景

  • 监听页面的滚动事件;
  • 鼠标移动事件;
  • 用户频繁点击按钮操作;
  • 游戏中的一些设计;

总之,依然是密集的事件触发,但是这次密集事件触发的过程,不会等待最后一次才进行函数调用,而是会按照一定的频率进行调用。


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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button id="btn">按钮</button>
</body>

</html>
<script>
  // 【这里,debounce中的this指向window】
  function debounce(func, wait = 500, immediate = false) {
    let timer = null;
    // anonymous、setTimeout的this都是指向btn
    return function anonymous(...params) {
      // 第一次触发 或 立即执行的条件:immediate为true,timer为null
      let now = immediate && !timer;
      clearTimeout(timer);
      timer = setTimeout(() => {
        timer = null;
        // 执行函数:注意保持this和参数的完整度,让定时器的this和anonymous保持一致 
        // 【this指向事件源,箭头函数没有自己的this】
        !immediate ? func.call(this, ...params) : null;
      }, wait);

      // 用于第一次触发 或 立即执行 
      now ? func.call(this, ...params) : null;
    };
  }


  // -----------------------------------------------------


  /*
   * 假设我们频率设置为500ms,我们频繁触发了10000ms,对于防抖,则只触发一次;对于节流,则触发20次
   * 
   * throttle:实现函数的节流(目的是频繁触发中缩减频率)
   *   @params
   *      func:需要执行的函数
   *      wait:自己设定的间隔时间(频率)
   *   @return
   *      可被调用执行的函数
   */
  // 【第一次触发时,remaining是负数,previous被赋值为当前时间。】
  // 【第二次触发,假设是在间隔20ms,则remaining = 500 - (新的当前时间 - 上一次触发时间) = 500 - 20 = 480,也就是定时器的等待时间remaining。】
  function throttle(func, wait = 500) {
    let timer = null;
    let previous = 0; // 记录上一次操作时间
    return function anonymous(...params) {
      let now = new Date(); // 当前操作的时间
      let remaining = wait - (now - previous); // 剩余时间
      // remaining <= 0 与 remaining > 0 的区别:后者多了个定时器,previous不一样
      if (remaining <= 0) {
        // 两次间隔时间超过频率:把方法执行即可
        // clearTimeout是从系统中清楚定时器,timer值不会变为null 【银行系统清理排队号】
        clearTimeout(timer);
        // 这是给变量timer赋值为null,就可以通过timer是否为null,判断有木有定时器 【自己把小纸条丢垃圾篓,也可以不扔,拿手里,但是就不能通过timer是否为null,判断有木有定时器了。】
        timer = null;
        previous = now; // 【把上一次操作时间修改为当前时间】
        func.call(this, ...params);
      } else if (!timer) {
        // 两次间隔时间小于频率,如果没有定时器,设置定时器;有定时器了,就不用重新设置定时器,而是以上一次的计时为准
        // 两次间隔时间没有超过频率,说明还没有达到触发标准,设置定时器等待即可(还差多久,就等多久) 【假设时间间隔是500ms,第20ms点击,剩余480ms,就等待480ms。】
        timer = setTimeout(() => {
          clearTimeout(timer);
          timer = null;
          // 过了remaining时间后,才去执行func,所以previous不能等于now
          previous = new Date(); // 【把上一次操作时间修改为当前时间】
          func.call(this, ...params);
        }, remaining);
      }
    };
  }

  function func() {
    console.log('OK');
  }

  // debounce中的this指向window,因为调用debounce,前面没有任何东西。注意,这里是调用函数,而不是定义函数,如果是定义函数,那么debounce中的this指向btn
  // btn.onclick = debounce(func, 500, true);
  // btn.onclick = func;
  btn.onclick = throttle(func, 1000);


  // -------------------------------------------


  //  防止频繁点击触发:设置标识进行判断 
  let isClick = false;
  btn.onclick = function () {
    // isClick为false,则不继续往下执行
    if (isClick) return;
    isClick = true;
    setTimeout(() => {
      console.log('OK');
      isClick = false;
    }, 500);
  };
</script>
原文地址:https://www.cnblogs.com/jianjie/p/13872633.html