理解防抖和节流的区别

背景:

在前端开发中,我们会经常需要绑定一些持续触发的事件,如resize, scroll, mousemove等,但是有时候不希望在事件持续触发的过程中太频繁地执行函数

 

<!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>
    <div
      id="content"
      style="
        height: 150px;
        line-height: 150px;
        text-align: center;
        color: #fff;
        background-color: #ccc;
        font-size: 80px;
      "
    ></div>
    <script>
      let num = 1;
      const content = document.getElementById("content");
      function count() {
        content.innerHTML = num++;
      }
      content.onmousemove = count;
    </script>
  </body>
</html>

 

在上述代码中,div 元素绑定了 mousemove 事件,当鼠标在 div(灰色)区域中移动的时候会持续地去触发该事件导致频繁执行函数

 

 

 

解决方案:防抖和节流

 

1. 防抖(debounce):是指触发事件后n秒才执行函数,如果在n秒内又触发事件了,则会重新计算函数时间

(case1:做电梯,电梯会等没人进来了,再等一会,才关闭;case2: input输入框,输入内容搜索,在停下输入过一会后,才发起请求获取匹配的数据;case3: 玩游戏读条,只有读条结束才可以输出,如果读条被打断,要重新读条) 

 

1.1 非立即执行版防抖:事件触发后不立即执行,n秒后执行

 

    <script>
      let num = 1;
      const content = document.getElementById("content");
      function count() {
        content.innerHTML = num++;
      }

      //   非立即执行版:触发事件后函数不会立即执行,而是在n秒后执行,
      //   如果n秒内又触发了事件,则会重新计算函数执行时间
      function debounce(func, wait) {
        let timeout;
        return function () {
          const context = this;
          const args = [...arguments];
          if (timeout) {
            clearTimeout(timeout);
          }
          timeout = setTimeout(() => {
            func.apply(context, args);
          }, wait);
        };
      }
      content.onmousemove = debounce(count, 1000);
    </script>

 

1.2 立即执行版防抖:触发事件后函数立即执行,n秒内不触发事件才能继续执行

 

      //   立即执行版: 触发事件后函数会立即执行,然后n秒内不触发事件才能继续执行的效果
      function debounce(func, wait) {
        let timeout;
        return function () {
          const context = this;
          const args = [...arguments];
          if (timeout) {
            clearTimeout(timeout);
          }
          const callNow = !timeout;
          timeout = setTimeout(() => {
            timeout = null;
          }, wait);
          if (callNow) {
            func.apply(context, args);
          }
        };
      }

 

1.3 结合版本:实际开发过程中,需要根据不同场景来决定使用哪一个版本的防抖函数。上述版本可以合并:

    
      /**
       * @desc 函数防抖
       * @param func 函数
       * @param wait 延迟执行毫秒数
       * @param immediate true 表立即执行,false 表非立即执行
       */

function debounce(func, wait, immediate) { let timeout; return function () { const context = this; const args = [...arguments]; if (timeout) { clearTimeout(timeout); } if (immediate) { const callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait); if (callNow) { func.apply(context, args); } } else { timeout = setTimeout(() => { func.apply(context, args); }, wait); } }; } content.onmousemove = debounce(count, 1000, false);

 

 

2. 节流(throttle):是指连续触发事件,但是在n秒中只执行一次的函数。节流会稀释函数的执行频率

(case 1: 看电影,每秒有24帧,意思是每1秒的动画,播放了24张连续的图片;case2: 滚动条加载更多,监听滚动条位置时,设置每1s监听一次,而不是无限次监听;case3: 节流相当于玩游戏某技能的cd,cd时间未到不会执行)

 

2.1 时间戳版节流:在持续触发事件的过程中,函数会立即执行,并且每1s执行一次

      function throttle(func, wait) {
        var previous = 0;
        return function () {
          let now = Date.now();
          let context = this;
          let args = arguments;
          if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
          }
        };
      }

 

2.2 定时器版节流:在持续触发事件的过程中,函数不会立即执行,每1s执行一次,在停止触发事件后,函数会再执行一次

      function throttle(func, wait) {
        let timeout;
        return function () {
          let context = this;
          let args = arguments;
          if (!timeout) {
            timeout = setTimeout(() => {
              timeout = null;
              func.apply(context, args);
            }, wait);
          }
        };
      }

 

2.3 时间戳版本节流和定时器节流的区别是,时间戳版本的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候。

 

两者合并,如下版本:

/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param type 1 表时间戳版,2 表定时器版
 */
function throttle(func, wait ,type) {
    if(type===1){
        let previous = 0;
    }else if(type===2){
        let timeout;
    }
    return function() {
        let context = this;
        let args = arguments;
        if(type===1){
            let now = Date.now();

            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }else if(type===2){
            if (!timeout) {
                timeout = setTimeout(() => {
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }

    }
}

 

3. 防抖和节流的区别

  • 用户在搜索的时候,在不停敲字,如果每敲一个字我们就要调一次接口,接口调用太频繁,给卡住了。-----用防抖
  • 用户在阅读文章的时候,我们需要监听用户滚动到了哪个标题,但是每滚动一下就监听,那样会太过频繁从而占内存,如果再加上其他的业务代码,就卡住了。-----用节流

 

防抖:结合输入框场景,只有当用户输入完毕一段时间后,才会调用接口,出现联想词

节流:指定时间间隔,只执行一次任务;应用场景:比如,懒加载监听滚动条的位置,使用节流按一定频率获取

 

使用防抖和节流可以减少不必要的损耗(频繁调取接口,网络堵塞,增加服务器压力)。

 

最后,汇总

函数防抖的时候,每次调用事件都是在正常执行暂停后一段时间
函数节流的时候,则是每隔一定的时间间隔就触发一次
 


 

参考:

https://juejin.cn/post/6844903651278848014

https://juejin.cn/post/6844904185117278215

 

posted @ 2021-04-20 14:01  jane_panyiyun  阅读(1826)  评论(0编辑  收藏  举报