Loading

详解防抖节流

背景

在平时的开发过程中,我们或多或少都会遇到如下两种情况:

情景一

当我们希望一个输入框的内容改变时就发起一次请求去请求新的数据,常见的例子就是搜索建议。但是如果我们输入的时候手抖了,多输了一个字符,然后快速把它删了,这时候浏览器实际会多发出两次请求

  • 一次是输多的那一下;
  • 一次是删除的那一下。

如何让浏览器等待我们输入好之后才请求呢?也就是会给我们一定的容错时间,答案是防抖

情景二

当我们希望每隔一段时间获取当前鼠标的位置。

获取鼠标位置当然就是监听鼠标的 mousemove 事件,但是浏览器对鼠标事件的监听频率很快,基本上一秒都会触发几十次,那么如果我们想要隔一秒获取一次鼠标的位置,怎么解决呢?答案是节流

防抖和节流是什么,如何实现它们,它们之间又有什么区别呢?下面挨个解释。

防抖(debounce)

防抖,顾名思义就是防止手抖。也就是说使用了它,会给我们一定的容错时间,如果在这个容错时间内再次触发事件了,我们就会直接撤销上一次误操作,直接开始新的事件。

它的核心思想是:当事件被触发的时候,设定一个容错时间延迟执行动作(计时器),若这期间再次被触发,则重新设定周期(清除上一次计时器,重新定时),直到定时器结束,才触发事件执行动作。

光说不练假把式,防抖的简单代码实现如下:

function debounce(func, delay) {
  let timer = null; // 计时器

  return function(...args) {
    clearTimeout(timer); // 清除上一次计时器

    timer = setTimeout(() => { // 重新定时
      func.apply(this, args);
    }, delay);
  };
}

问题一:这里为什么要使用闭包呢?

原因是当我们每一次进入返回的函数体时都要清除上一次的计时器,因此计时器变量需要放到返回的函数外部形成闭包,做到定时器共享,也就是下一次执行函数能访问到上一次赋值的计时器。

问题二:为什么要使用 func.apply(this, args) 而不直接使用 func()

原因是当我们每次调用函数时都需要确保执行上下文正确,因此需要显式绑定 this;同时函数调用一般都需要传递参数,因此直接使用 apply 语法就行了。

节流(throttle)

节流,顾名思义就是节省流量。我们使用了它,则函数在一定时间内只会执行一次,如果在这段时间内再次触发事件了,就会直接无视,直到这段时间结束。

节流的核心思想是:在固定一段时间内,只执行一次函数,若这段时间有新事件触发,则选择不执行。当这段时间结束后,又有事件触发,才再次开始一段新的周期。

下面节流的代码同样采用定时器实现:

function throtte(func, time) {
  let timer = null; // 计时器

  return function(...args) {
    if (timer) return; // 无视,直接返回

    timer = setTimeout(() => {
      func.apply(this, args);
    }, time);
  };
}

防抖和节流的区别

如果理解了上面,肯定可以清晰的看出防抖和节流的区别以及具体的应用场景,下面比喻一下它们流程的区别:

  • 防抖类似军训:如果小冬站着不动,10 分钟之后就可以休息,但是如果他这 10 分钟内动了,那么就要重新计时,直到什么时候时候他坚持 10 分钟内不动才能休息。

  • 节流类似面试:一次只能一个人面试,并一次面试 20 分钟,如果其他人打算面试的时候发现有人正在面试,那么他只能等待,直到上一个人面试完才能由下一个人面。

posted @ 2023-02-19 18:39  touryung  阅读(89)  评论(0编辑  收藏  举报