防抖,节流

212 阅读4分钟

不管是在日常的开发工作中,还是在前端学习中,都经常会听到防抖和节流这样的专业名词,那么防抖和节流到底是何方神圣呢?本篇文章就和大家一起来讨论讨论,揭开他们神秘的面纱。

防抖(debounce)

定义:给定两个条件,一个函数,一个指定的时间段,只有该函数在指定时间段内不再被调用,才会在最后执行该函数。

什么?没听懂?举个栗(例)子🌰

饭店大家应该都去过吧,假设现在已经到厨师下班点了,厨师下班前的任务是收拾干净厨房,收拾厨房就是函数,如果半个小时内都没有来客,那么就执行收拾厨房下班的任务,这半个小时就是指定的时间段,如果此时厨师已经等待了20分钟,在第21分钟时来了客人,厨师就必须得炒菜,并且骂骂咧咧,之后继续等待半个小时,如果中途来新客人,又要清除半个小时并重新开始,直到半个小时内都再没有来客,厨师才会打扫厨房下班。

相信通过栗子,聪明的你们也能猜到防抖是如何实现的

实现原理:没错,就是延时器,setTimeout,如果在函数触发时检测到了延时器,那么就把延时器清除,重新定义一个新的延时器,只有在指定的延时内不触发函数,任务才会在指定的延时后执行。

input输入框是利用防抖进行优化的一个典型,如下是没有进行防抖优化的

1667809756244.png 可以看到,每一次输入都会调用task函数,如果task执行ajax请求或者是需要大量计算的任务,那么这种代码的性能是很差的,这种时候就可以使用防抖来进行优化,优化如下:

1667813625016.png

1667813707065.png debounce函数就是专门用作防抖的函数,由于需要缓存一个timer,所以这边使用闭包处理,可以看到,防抖处理之后并没有在每次输入时执行task,只在无输入的500ms后执行,处理window的resize监听也是同理。

节流(throttle)

定义:同样两个条件,函数在指定的时间段内只会执行一次

听懂了我也要举个栗(例)子🌰

疫情当下,大家都有过排队做核酸的经历吧,假设医护人员做核酸的速度是固定的,1分钟6个人,那么这1分钟就是指定的时间段,给6个人做核酸就是要执行的任务,为了防止核酸窗口拥堵,工作人员会每隔1分钟放行6个人,那么工作人员所做的事情就叫节流。

实现原理:在延时器的基础上增加开关控制器,在节流函数第一次调用时创建一个延时器,并将控制器关闭,在指定的延时后执行函数并将控制器打开。

比如在窗口resize时进行节流

1667874898115.png

1667875209162(1).png throttle函数就是节流函数,同样使用闭包来缓存一个控制器,可以看到,窗口在不断resize中,每两秒才会执行一次resize这个函数。

防抖和节流对高频事件的处理是非常有效的

其实写到这里防抖和节流的基本功能就已经实现了,不过深入探究会发现这样写的防抖和节流都是有优化空间的,防抖函数如果一直高频触发的话,目标函数就一直无法执行,节流函数如果在指定时间段内只触发了一次,那么目标函数还是得在延迟指定时间段后执行。

基于这两个问题,我们可以思考是否可以将防抖和节流组合起来使用,并且加上立即执行的功能?OK,直接冻(动)手!!

防抖与节流的组合技

代码如下: 1667893803656.png 组合技代码接收第三个参数,用来做配置项,这里我在防抖代码的基础上增加时间戳来实现节流的功能,每次防抖阶段结束时,都会将组合技函数还原为初始状态,以便下一次触发时不受上一次状态的影响。

调用结果如下: 1667894387323.png

完整代码我贴在下方,需要者请自取

function combo(func, wait, options) {
  let timer = null;
  let timestamp = 0;
  return function() {
    const now = Date.now();
    clearTimeout(timer);
    if(!timer) timestamp = now;
    if(!timer && options?.immediate) func.call(this, ...arguments);
    if(options?.timeout) {
      if(options.timeout <= wait) {
        throw new Error('超时时间不得小于等待时间');
      } else if(now - timestamp > options.timeout) {
        func.call(this, ...arguments);
        timestamp = now;
      }
    }
    timer = setTimeout(() => {
      func.call(this, ...arguments);
      timestamp = 0;
      timer = null;
    }, wait);
  };
};

对options配置项值的校验还请自行处理,这里就不再赘述了

前端小渣渣的学习总结,如有错误,欢迎指正!!