打开网易新闻 查看更多图片

在JavaScript应用中经常需要处理性能问题。

节流(throttle)和去抖动(debounce)让我们能够控制调用函数的频率。对于Web开发者来说应该掌握这两种技术。当我们处理事件处理器(event handler)时,它们特别有用。有些情况下,我们可能在不必要的时候调用了函数。例如,我们希望在调整窗口大小时执行一个回调函数。在我们调整大小时触发回调有意义吗?很可能不是我们想要的。我们希望等到用户完成操作后,再调用回调函数。

节流和去抖动的区别是什么?

· 节流:一个形象的比喻是在酒吧点酒。你去酒吧,吧台服务员有一个规矩,只允许你每45分钟点一杯酒(否则会坏事)。你在第一分钟点了一杯,他们递过来一杯。然后你试着每分钟点一杯。服务员会拒绝你,直到第45分钟,这时服务员递过来下一杯酒。接下来的45分钟你就不能再点一杯了。使用节流时,你可能希望在节流结束后进行最后一次调用,因为你在节流期间被拒绝了至少一次。想象一下,你在第15分钟点了一杯酒,却被拒绝了。在第45分钟,你没有点酒,但是一个服务员会过来,把第15分钟点的酒端上来。他们觉得很抱歉。

· 去抖动:它和节流有点不同,它会在一切就绪时再执行。设想一下在餐馆点餐的场景。你开始点菜了,服务员在一旁记录,会问你“还需要别的吗?”,如果你点完了,他们会离开并为你端上饭菜。如果还没点完,他们会为你继续点菜并再次问你,一直到完成。

使用场景

节流的使用场景:

· 快速点击一个按钮时防止事件监听器函数触发太频繁

· 限制API调用的频率

· 限制 mousemove/touchmove 事件监听器函数执行的频率

去抖动的使用场景:

· resize 事件监听器

· scroll 事件监听器

· 自动保存功能

我们将为二者各举一个例子。节流可能比去抖动用得少。通常情况下,当你考虑使用节流时,用去抖动可能会更好。

对于节流,让我们考虑第一个用例,防止用户疯狂点击。我们的应用中有一个按钮,当点击它时,会调用某个API。通过节流,我们可以限制调用API的次数。用户可能每秒点击20次,但我们每秒只触发一次事件处理器。

对于去抖动,让我们考虑一下自动保存功能。每次用户进行更改或交互时,自动保存都会尝试保存应用当前的状态。我们可以延迟保存的操作,直到用户在一段时间内没有进行任何更改或交互。这样我们就不会执行过多无谓的执行操作。这将有助于提高性能。

节流和去抖动的代码实现

节流和去抖动有许多不同的实现方式。它们都会用到 setTimeout 函数。

去抖动 debounce

去抖动的代码相对较简单。

打开网易新闻 查看更多图片

我们把函数(func)和延迟(delay)传入debounce函数中。inDebounce变量用来记录延迟处理的任务。

如果我们是第一次调用,函数将在延迟结束时执行。如果我们在延迟结束之前再次调用,延迟就会重新开始。

我们可以这样使用 debounce 函数:

打开网易新闻 查看更多图片

在上面的例子中,我们会延迟3秒输出当前日期。

节流 throttle

节流相对有点复杂,因为它期望的行为有不同的解释。让我们从限制函数的执行频率开始。

打开网易新闻 查看更多图片

首次调用我们的函数会执行,然后设置限制保存在 inThrottle 变量里。在随后的限制期内,你可以调用函数,但是不会触发,一直到 throttle限制期结束。限制期过后,下次函数调用会执行,然后再次进入限制期。

打开网易新闻 查看更多图片

但是最后一次函数调用怎么办?假如正好处于限制期,它会被忽略,不过如果这不是我们想要的效果怎么办?例如,我们监听mousemove 事件来调整元素的大小,如果不执行最后一次函数调用,不会得到期望的效果。我们需要处理这种情况,在限制期过后调用它。

打开网易新闻 查看更多图片

上述代码确保了可以执行最后一次函数调用。我们创建了一个变量 lastRun 用于保存最后一次调用的时间戳。随后可以用它来识别最后一次调用是否发生在限制期内。我们还可以用 lastRun 来确定节流函数是否已运行。所以我们不再需要上个例子中的inThrottle 变量。

另一种理解这个throttle函数的思路是:它是一个 debounce 链。每次 debounce 的限制期在缩短。throttle有一些有趣的扩展用途,例如,你可以存储所有被忽略的函数调用,最后依次调用它们。

结语

节流和去抖动是两种Web开发中必须掌握的技术,它们都会限制函数调用的频率。合理的使用会有效提升应用的性能。