应对 JavaScript 应用程序中的性能问题经常会涉及到节流和防抖。
节流和防抖让我们可以控制函数被调用的速率。对于任何网络开发人员来说,它们都是必备技能。当我们处理事件处理程序分配时,它们尤其有用。有些情况下,我们可能会在不必要的情况下调用函数。考虑一个回调函数,我们想要在窗口大小调整时执行。在我们调整大小时触发回调函数有意义吗?很可能不是。我们希望等到用户完成交互后再触发回调函数。
对于那些不想阅读太多的人,这里有一个演示!
节流和防抖的区别是什么?
- 节流 — 如果你想到汽车油门。你踩下脚的力度限制了进入发动机的汽油量。在这种情况下,我们想要限制函数被调用的数量。我觉得更好的比喻是彩票,每五秒钟只有一个球被抽出。或者更好的比喻是在酒吧点饮料。你去酒吧,酒保规定你每45分钟只能点一杯饮料(否则事情会变得疯狂)。你在第一分钟点了一杯饮料,他们就给了你一杯。然后你试着每分钟点一次。酒保拒绝你,直到第45分钟,疲惫的酒保才把下一杯饮料递给你。你再等45分钟才能再喝一杯。在节流之后,你可能希望最后一个调用发生在节流结束后。这将是被拒绝的调用之一。想象一下你在第15分钟点了一杯饮料并被拒绝了。在第45分钟,你没有点饮料,但酒保派了一个服务员,给你送了那15分钟点的饮料。他们为你感到难过😁
- 防抖 — 防抖的工作方式略有不同。它可能有点难以解释。使用防抖时,就像是“嘿,我不会执行那个函数,直到我知道没有更多的更改入站”。在其他人都满意并且我们可以继续时,我们才执行我们的函数。想象一下在餐厅点餐。你开始向服务员列出项目,最后他们问:“还有吗?”如果有,你就添加到订单中,然后他们再次问你,直到他们可以继续。
这是一个节流和防抖的可视化演示,可能有助于理解。移动鼠标或手指以开始节流和防抖事件处理程序👍
方块代表节流的函数调用。圆圈代表防抖的函数调用。
示例用例
- 节流按钮单击,以防止连续单击
- 节流 API 调用
- 节流
mousemove
/touchmove
事件处理程序 - 防抖
resize
事件处理程序 - 防抖
scroll
事件处理程序 - 防抖自动保存功能中的保存函数
让我们考虑每个示例。节流可能比防抖使用较少。在考虑节流使用时,你可能更适合使用防抖。如果你有一个好的节流用例,请告诉我!
对于节流,让我们首先考虑阻止单击垃圾邮件的第一个用例。我们在应用程序中有一个按钮,当点击它时,会进行某种 API 调用。比如参加比赛。使用节流,我们可以限制 API 被调用的次数。用户可能每秒钟点击20次,但我们每秒钟只触发一次处理程序。
对于防抖,让我们考虑自动保存功能。自动保存功能尝试在用户更新或交互时保存应用程序的状态。我们可以防抖保存,直到用户没有进行任何更新或交互一段时间。这样我们就不会垃圾邮件保存函数并进行不必要的保存。这将有助于性能。
实现节流和防抖
有各种实现throttle
和debounce
的方法。大多数都可以实现相同的目标。它们的实现都围绕使用setTimeout
。
对于那些只想要演示的人,我已经为你准备好了😎
在此演示中,函数通过2000ms
和3000ms
进行了防抖和节流。
防抖
防抖比节流更简单。
我们将一个函数(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
具有一些有趣的可能性。例如,你可以存储所有被忽略的执行,并按顺序在最后运行它们。
结论
对函数执行进行节流和防抖动是需要知道的技术。它们可以显着提高应用程序的性能。
评论(0)