函数的节流和防抖

9 分钟读完

什么是节流和防抖

我觉得描述节流和防抖最直观的方式是举个实际的例子。

节流

假设网页中有一个下载按钮,用户点击后会开始下载指定的资源。

但是现实中有一种情况——用户可能会连续、快速地点击多次(可能因为有延迟所以暂时没反应),如果我们不进行处理,就会连续下载很多次,这是一种不好的用户体验。

这时有一种比较好的处理方案:用户的第一次点击是生效的,然后在一定时间内(假设为1秒),不管用户点击多少次,都不会生效。1秒后,如果用户又点击了一次,我们就再生效一次。

这样,每一秒内最多只能有一次点击真正生效,也就是所谓的“节流”

防抖

假设我们在网页中想实现一个功能:

当用户输入文本时,我们自动向后台发送请求,将用户输入的文本保存起来。

如果不进行任何处理,用户每进行一次输入就会发送一次请求,这样就会导致请求发送的太多。有没有什么解决方案呢?

肯定是有的,那就是当用户输入开始后,只有当用户在一定的时间间隔内没有输入,我们才发送请求。如果用户持续输入,我们就不发送。(在这种场景下这种方案是很好的,因为用户输入总会有停顿和停止的时候)

还有一些情况下防抖更有用:比如我们想监听网页滚动的事件,如果监听到就调用一个函数,如果不作任何处理,网页可能每滚动一个像素都要调用函数,我们完全可以设置当滚动发生后,如果滚动停止了一小段时间,我们再调用函数。

如何实现节流函数

节流函数的实现思路是:

  1. 调用节流后的函数

  2. 检查该函数之前是否调用过(通过设置一个时间戳)

    1. 如果该函数从来没调用过:

      1. 执行该函数
      2. 设置一个时间戳
    2. 如果该函数调用过:

      ​ 检查现在距离上次调用时间过去了多久:

      ​ 如果已经超过了我们限制的某个时长,就执行该函数,再设置一个时间戳

      ​ 如果没有超过,就什么事情都不作

在开始代码之前,我们先来快速了解一下JavaScript中的闭包的特点,对于下面这一段代码:

如果你对闭包没有了解,请先去看看javascript闭包

function fn(b) { //这个函数会返回一个函数
  var a = 1;
  return function () { //这个返回的函数是一个闭包
    a++;
    console.log(a);
  }
}

let func = fn(); //获取一个闭包函数

//下面开始调用这个闭包函数
console.log("第1次调用")
func(); 
console.log("第2次调用")
func();
console.log("第3次调用")
func();
console.log("第4次调用")
func();

得到的是这样的输出结果

第1次调用
2
第2次调用
3
第3次调用
4
第4次调用
5

注意这个输出结果说明了:

一个闭包函数(即func)不管被调用多少次,都共享同样的闭包资源(即fn中的a变量)。

下面再来看具体的实现:

const throttle = function (fn, limit) {
  //这个throttle函数会返回一个函数,我们不妨将返回的函数称作返回函数,即闭包函数

  let prevTime = null; //返回函数上一次被调用的时间

  //返回一个函数,这个函数的作用是:
  //如果满足一定的条件,就设置一个定时器,定时器中执行fn
  return () => {
    //这个函数只有在throttle函数被调用的时候才会执行
    //获取这个函数被调用时的作用域和参数,(存下来传递给fn)
    let context = this;
    let args = arguments;

    if (!prevTime) {
      //如果prevTime为null,说明返回函数被第一次调用
      //第一次调用我们直接执行fn
      fn.apply(context, args);
      //记录当前这次执行fn的时间
      prevTime = Date.now();
    } else {
      //如果之前返回函数已经被调用过了,则看看距离上次调用是否过去足够长的间隔了。
      //现在我们要获取本次调用距离上次调用的时间间隔
      let hasPass = Date.now() - prevTime;

      if (hasPass >= limit) { //如果时间间隔大于limit,则直接调用fn
        fn.apply(context, args);
        prevTime = Date.now();
      }
      //否则什么都不做

    } //end of else => (!prevTime)  

  }; //end of return 

}

如何实现防抖函数

如果你忘了什么是防抖函数,可以先去本文开始的地方看看。

防抖函数的实现思路是:

  1. 调用防抖后的函数
  2. 清除之前设置的定时器(如果有的话)
  3. 设置一个新的定时器,定时器的作用是在一定的时间后执行我们想执行的函数。(并存下来这个定时器)

实现思路很简单,下面是代码:

const debounce = function (fn, delay) {
  //这里的逻辑是,每当返回函数被调用时,我们就清除之前设置的timer,重新设置一个新的timer
  let prevTimer = null;
  return () => {
    clearTimeout(prevTimer);
    let context = this;
    let args = arguments;
    prevTimer = setTimeout(() => {
      fn.apply(context, args);
    }, delay);
  }
}

更新时间: