首页
Preview

JavaScript 中的节流和防抖

应对 JavaScript 应用程序中的性能问题经常会涉及到节流和防抖。

节流和防抖让我们可以控制函数被调用的速率。对于任何网络开发人员来说,它们都是必备技能。当我们处理事件处理程序分配时,它们尤其有用。有些情况下,我们可能会在不必要的情况下调用函数。考虑一个回调函数,我们想要在窗口大小调整时执行。在我们调整大小时触发回调函数有意义吗?很可能不是。我们希望等到用户完成交互后再触发回调函数。

对于那些不想阅读太多的人,这里有一个演示!

节流和防抖的区别是什么?

  • 节流 — 如果你想到汽车油门。你踩下脚的力度限制了进入发动机的汽油量。在这种情况下,我们想要限制函数被调用的数量。我觉得更好的比喻是彩票,每五秒钟只有一个球被抽出。或者更好的比喻是在酒吧点饮料。你去酒吧,酒保规定你每45分钟只能点一杯饮料(否则事情会变得疯狂)。你在第一分钟点了一杯饮料,他们就给了你一杯。然后你试着每分钟点一次。酒保拒绝你,直到第45分钟,疲惫的酒保才把下一杯饮料递给你。你再等45分钟才能再喝一杯。在节流之后,你可能希望最后一个调用发生在节流结束后。这将是被拒绝的调用之一。想象一下你在第15分钟点了一杯饮料并被拒绝了。在第45分钟,你没有点饮料,但酒保派了一个服务员,给你送了那15分钟点的饮料。他们为你感到难过😁
  • 防抖 — 防抖的工作方式略有不同。它可能有点难以解释。使用防抖时,就像是“嘿,我不会执行那个函数,直到我知道没有更多的更改入站”。在其他人都满意并且我们可以继续时,我们才执行我们的函数。想象一下在餐厅点餐。你开始向服务员列出项目,最后他们问:“还有吗?”如果有,你就添加到订单中,然后他们再次问你,直到他们可以继续。

这是一个节流和防抖的可视化演示,可能有助于理解。移动鼠标或手指以开始节流和防抖事件处理程序👍

方块代表节流的函数调用。圆圈代表防抖的函数调用。

示例用例

  • 节流按钮单击,以防止连续单击
  • 节流 API 调用
  • 节流 mousemove/touchmove 事件处理程序
  • 防抖 resize 事件处理程序
  • 防抖 scroll 事件处理程序
  • 防抖自动保存功能中的保存函数

让我们考虑每个示例。节流可能比防抖使用较少。在考虑节流使用时,你可能更适合使用防抖。如果你有一个好的节流用例,请告诉我!

对于节流,让我们首先考虑阻止单击垃圾邮件的第一个用例。我们在应用程序中有一个按钮,当点击它时,会进行某种 API 调用。比如参加比赛。使用节流,我们可以限制 API 被调用的次数。用户可能每秒钟点击20次,但我们每秒钟只触发一次处理程序。

对于防抖,让我们考虑自动保存功能。自动保存功能尝试在用户更新或交互时保存应用程序的状态。我们可以防抖保存,直到用户没有进行任何更新或交互一段时间。这样我们就不会垃圾邮件保存函数并进行不必要的保存。这将有助于性能。

实现节流和防抖

有各种实现throttledebounce的方法。大多数都可以实现相同的目标。它们的实现都围绕使用setTimeout

对于那些只想要演示的人,我已经为你准备好了😎

在此演示中,函数通过2000ms3000ms进行了防抖和节流。

防抖

防抖比节流更简单。

我们将一个函数(func)和一个延迟(delay)传递到防抖函数中。inDebounce是我们用来跟踪延迟时间的变量。如果我们第一次调用,函数将在延迟结束时执行。如果我们在延迟结束之前再次调用,延迟将重新开始。通过阅读代码并玩弄演示,理解起来会更容易😉。

以下是debounce的实际效果。

debounceBtn.addEventListener('click', debounce(function() {
  console.info('Hey! It is', new Date().toUTCString());
}, 3000));

在此示例中,我们通过3秒来防抖动调用,此时我们会打印出日期。

节流

节流可能有点费劲,因为其所需的行为有不同的解释。让我们从限制执行函数的速率开始。

const throttle = (func, limit) => {
  let inThrottle
  return function() {
    const args = arguments
    const context = this
    if (!inThrottle) {
      func.apply(context, args)
      inThrottle = true
      setTimeout(() => inThrottle = false, limit)
    }
  }
}

对函数的第一次调用将执行并设置限制期间inThrottle。我们可以在此期间调用函数,但在throttle期间过去之前,它不会触发。一旦过去,下一次调用将触发,过程重复。

throttleBtn.addEventListener('click', throttle(function() {
  return console.log('Hey! It is', new Date().toUTCString());
}, 1000));

但是我们的最后一次调用呢?如果它在限制期间内,它将被忽略,如果我们不想要这个结果怎么办?例如,如果我们绑定到鼠标移动进行调整大小,并错过了最后一次调用,我们将永远无法获得所需的结果。我们需要捕获并在限制期间之后执行它(感谢worldwar质疑之前的实现,该实现并不总是按预期的<em>100%</em>工作)。

const throttle = (func, limit) => {
  let lastFunc
  let lastRan
  return function() {
    const context = this
    const args = arguments
    if (!lastRan) {
      func.apply(context, args)
      lastRan = Date.now()
    } else {
      clearTimeout(lastFunc)
      lastFunc = setTimeout(function() {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(context, args)
          lastRan = Date.now()
        }
      }, limit - (Date.now() - lastRan))
    }
  }
}

此实现确保我们捕获并执行最后一次调用。我们还在正确的时间调用它。我们通过创建一个变量lastRan来实现这一点,该变量是最后一次调用的时间戳。然后,我们可以使用它来确定上次调用是否在throttle限制内发生。我们还可以使用lastRan来确定是否已运行限制的函数。这使得以前的变量inThrottle变得多余。

这种throttle的实现方法可以被视为链接debounce。每次debounce等待时间缩短。throttle具有一些有趣的可能性。例如,你可以存储所有被忽略的执行,并按顺序在最后运行它们。

结论

对函数执行进行节流和防抖动是需要知道的技术。它们可以显着提高应用程序的性能。

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
一个人玩
先找到想要的,然后出发

评论(0)

添加评论