Skip to content

一文搞懂手写防抖函数以及优化方案

20230724 手写防抖函数-百度前端一面

1.防抖函数

要求:点击一个 button,点击的时候在一定的时间内避免重复触发 ——> 防抖

初代版本:期限过了以后再执行,如果一直点就一直不执行

1.第一个版本:行内绑定 js 函数的方式(不能用闭包)

html
<body>
  <div>
    <!-- <button onclick="fun">点我</button> -->
    <!--这样不行的,因为html行内绑定的函数是在具体触发的时候才执行,因为不是js,所以当然不会立即执行,而react本质是js,所以可以直接绑定不用加(),在js里面用addEventListener也和react一样不用加()-->
    <button onclick="fun()">点我</button>
  </div>
  <script>
    let timer = null;
    const fun = () => {
      clearTimeout(timer); //先清除定时器
      timer = setTimeout(() => {
        //再重新开一个
        console.log(111);
      }, 1000);
    };
  </script>
</body>

2.第二个版本:js 内监听事件绑定函数

html
<body>
  <div>
    <button id="debounceButton">点我</button>
    <!-- <button onclick="fun()">点我</button> -->
  </div>
  <script>
    let timer = null;
    const fun = () => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        console.log(111);
      }, 1000);
    };
    let button = document.getElementById("debounceButton");
    // button.addEventListener("click", fun()) //错误,会立即执行
    button.addEventListener("click", fun); //不用加()
  </script>
</body>

改写为闭包形式:

html
<body>
  <div>
    <button id="debounceButton">点我</button>
    <!-- <button onclick="fun()">点我</button> -->
  </div>
  <script>
    const fun = () => {
      let timer = null;
      return () => {
        clearTimeout(timer);
        timer = setTimeout(() => {
          console.log(111);
        }, 1000);
      };
    };
    let button = document.getElementById("debounceButton");
    button.addEventListener("click", fun()); //要加(),因为是闭包函数(高阶函数)
  </script>
</body>

优化后版本:立即执行,期限内不再执行(如果一直点就一直不再执行) ——> 符合实际的用户场景需求

html
<body>
  <div>
    <button id="debounceButton">点我</button>
  </div>
  <script>
    //闭包写法:传入一个处理函数 ——> 闭包只能用debounceButton.addEventListener这个写法
    function debounce(func, delay) {
      let timeoutId; //相当于null

      return function (...args) {
        console.log(timeoutId); //定时器id号
        clearTimeout(timeoutId); //只是会清除定时器的计时,但是不会把timeoutId设置为空!
        console.log(timeoutId); //还是定时器id号不变

        // 在防抖函数中,第一次点击会立即执行一次处理函数
        if (!timeoutId) {
          func.apply(this, args); //func.apply(this, args) 是 JavaScript 中的一种调用函数的方式,它的含义是调用函数 func 并传入指定的 this 上下文和参数数组 args
        }

        timeoutId = setTimeout(() => {
          timeoutId = null; // 防抖时间到,重置 timeoutId,可以再次立即执行(这里是用timeoutId来做这个标志变量,没有timeoutId了才可以执行处理函数,也可以用flag来做这个事情)
        }, delay);
      };
    }

    function handleClick() {
      console.log("按钮点击事件触发!");
      // 在这里添加你想要执行的代码(实际上应该是ajax请求方法)
    }

    const debounceButton = document.getElementById("debounceButton");

    // 添加按钮点击事件监听器,先立即执行一次处理函数,后续点击进行防抖处理
    debounceButton.addEventListener("click", debounce(handleClick, 1000));
    //这里js的绑定和html内的绑定不一样,这里绑定会立即执行,而html内是触发事件之后再执行
  </script>
</body>

那么节流函数怎么写?

html
<body>
  <button id="myButton">Click Me</button>
  <script>
    function throttle(func, delay) {
      let lastTime = 0;
      return function (...args) {
        const currentTime = new Date().getTime(); //获取现在事件
        if (currentTime - lastTime >= delay) {
          //如果现在时间与最后一次执行时间之差超过了期限,就可以再次执行!
          func.apply(this, args);
          lastTime = currentTime; //记录最后执行时间
        }
      };
    }

    // 点击事件处理函数
    function onClick() {
      console.log("Button clicked!");
    }
    // 获取按钮元素
    const button = document.getElementById("myButton");
    // 绑定点击事件
    button.addEventListener("click", throttle(onClick, 1000));
  </script>
</body>

注意,这里不像防抖一样需要优化为一上来就执行一次,因为节流函数一定会一上来就执行一次,因为最开始的 lastTime 为 0!

防抖和节流的区别:

其实防抖就是设置一个期限,期限内只能触发一次,如果重复点击就会重开期限!也就是说期限内不能再点击,点击了就不能再次触发了! **——> 一直连点不能多次触发,**只能触发一次

而节流是设置一个期限,也是期限内只能触发一次,但是重复点击不会重开期限,期限过了之后就可与再次触发!——> 一直连点可以多次触发,只要过了期限即可,就像把水流一股一股地放出来

官方说法:对于节流(Throttling),与防抖(Debouncing)相似,它也是控制函数触发频率的一种方法。不同之处在于,节流是确保函数在一定时间间隔内最多执行一次,而防抖是在函数连续触发后的一段时间内只执行一次。

使用场景

防抖的主要特点是在一定时间内多次触发函数时,只有最后一次触发会被执行,之前的触发都会被取消,从而实现延迟执行函数的效果。防抖通常是在用户操作的最后一次触发时执行函数,适用于例如搜索建议、输入框输入后提交等场景。(初代防抖)

优化的防抖适合于:点击按钮请求 ajax 数据的情况,避免用户一直点击造成一直请求!

防抖通常用于限制用户不正常行为 或者 错误连点操作 或者 为了最后统一执行

节流的主要特点是确保函数在一定时间间隔内最多执行一次。在节流函数中,即使在指定的时间间隔内多次触发函数,函数只会在每个时间间隔内的第一次触发时执行一次,后续的触发会被忽略。节流通常用于限制事件处理函数的执行频率,防止事件频繁触发,适用于例如滚动事件、resize 事件等场景。

2.节流函数

节流函数用于限制函数的执行频率,确保函数在一定时间间隔内只能执行一次。

js
let flag = false;
const test2 = () => {
  if (!flag) {
    flag = true;
    setTimeout(() => {
      console.log("执行!");
      flag = false;
    }, 1000);
  }
};

如果是封装的呢?

js
const test2 = (fun, time) => {
  let flag = false;
  return () => {
    if (!flag) {
      flag = true;
      setTimeout(() => {
        fun(this, arguments);
        flag = false;
      }, time);
    }
  };
};