js实现防抖函数

56 阅读4分钟

为什么需要防抖

在浏览器的某些操作中,有些函数会在短时间内频繁触发,但实际上我们不需要它一直被触发,比如:keyDown事件:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。我们按下键盘,只想触发一次,但是即使按下就松手也可能会触发好几次keyDown事件,所以这个场景写了一个防抖函数,防止多次触发keyDown事件。

还有比如一个input搜索框的模糊搜索,每次输入完要请求一次接口,这无疑会对服务器造成压力,所以这里防抖就可以设置比如在2秒内如果没有重新继续输入的话,就请求一次接口,而不是一输完立马发起请求。

什么是防抖函数

任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。也就是说,在某设定的时间内,没有再次触发某个函数时,才真正的调用这个函数,以防止函数过于频繁的不必要的调用。

可以类比现实生活中的电梯,无论你按了多少次按钮,电梯只会在最后一次按下按钮后的固定时间后才会启动。

  • 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;
  • 当事件密集触发时,函数的触发会被频繁的推迟;
  • 只有等待了一段时间也没有事件触发,才会真正的执行响应函数;
    image.png 核心思路如下
  • 当触发一个函数时,并不会立即执行这个函数,而是会延迟(通过定时器来延迟函数的执行)
    • 如果在延迟时间内,有重新触发函数,那么取消上一次的函数执行(取消定时器);
    • 如果在延迟时间内,没有重新触发函数,那么这个函数就正常执行(执行传入的函数);

接下来,将思路转成代码即可:

  • 定义debounce函数要求传入两个参数
    • 需要处理的函数fn;
    • 延迟时间;
  • 通过定时器来延迟传入函数fn的执行
    • 如果在此期间有再次触发这个函数,那么clearTimeout取消这个定时器;
    • 如果没有触发,那么在定时器的回调函数中执行即可;

代码实现

非首次立即执行

const debounce = (func, wait) => {//func是需要做防抖处理的函数
  let timer = null;
  return function(...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, wait);
  };
}

首次立即执行

const debounce = (func, wait = 50, immediate = false) => {
      let timer = null;
      return (...args) => {
        if (immediate) {
          // 如果设置了立即执行,且之前没有定时器在运行,则立即执行函数
          const callNow = !timer;
          clearTimeout(timer); // 清除之前的定时器
          timer = setTimeout(() => {
            timer = null; // 重置定时器
          }, wait);
          if (callNow) {
            func.apply(this, args);
          }
        } else {
          // 如果没有设置立即执行,清除之前的定时器,并设置新的定时器
          if (timer) {
            clearTimeout(timer);
          }
          timer = setTimeout(() => {
            func.apply(this, args);
          }, wait);
        }
      };
    };

debounce函数是什么意思

一个函数名后面如果有括号就代表执行的意思,因此函数名+() ,debounce()就是立即调用,但是如果直接调用debounce函数,它仅仅是返回了一个function,程序走不到return的函数中去,并不会起到防抖的效果。
我们可以这样调用

const debounce = (func,wait) =>{
    return {}
}

const _debounce = debounce(fn,500);
//在需要执行fn的地方做防抖处理调用debounce即可

改变fn()内部this的指向

如果什么都不改,fn内部的this指向的是全局的window,它既然作为我们真正的回到函数中要执行的部分,肯定要修改它里面的this指向,因此可以选择call(),apply(),bind()三种方法来实现这个需求。
因为在箭头函数内部用到this需要考虑到上下文(箭头函数没有内置的this),所以在这个地方,箭头函数的this直接继承父级函数function中的this。

call(),apply(),bind()区别

  • call()接受参数列表
  • apply()接受数组
  • bind()返回一个新的函数,可以声明变量接收

关于闭包

闭包是内部函数访问外部函数变量的集合。

因为闭包带来的其作用域链中引用的上层函数变量声明周期延长的效果,debounce函数的 settimeout计时器IDtimeout变量可以在debounce函数执行结束后依然留存在内存中,供闭包使用。
timer的声明如果在真正的回调函数内部,那么每次触发都会创建一个新的tiemr,清楚定时器操作也就没有意义了。

参考链接1:blog.csdn.net/Yuanyuan__/…

参考链接2:juejin.cn/post/713316…