节流和防抖是用于控制函数执行频率的两种常见技术。节流和防抖的目的都是为了优化性能和提高用户体验。 节流是指在一定时间间隔内,只执行一次函数。当频繁触发函数时,节流可以确保函数只被执行一次,从而减少不必要的计算和资源消耗。例如,在用户连续滚动页面时,可以使用节流来限制滚动事件的触发频率,以避免过多的计算和渲染。 防抖是指在一定时间间隔内,只执行最后一次触发的函数。当连续触发函数时,防抖可以延迟函数的执行,直到最后一次触发完成。这对于处理用户输入或者频繁点击事件非常有用,可以避免不必要的重复操作。例如,在用户输入搜索关键词时,可以使用防抖来延迟搜索请求的发送,以减少不必要的网络请求。 节流和防抖的选择取决于具体的应用场景和需求。节流适用于需要控制函数执行频率的场景,而防抖适用于需要延迟函数执行的场景。根据实际需求,选择合适的技术可以提高应用的性能和用户体验。

521

287
这是一个很好的可视化工具。 - thriqon
一个非常简单的例子,帮助我理解。 https://jsfiddle.net/Voronar/sxjy25ew/1/ - Kirill A. Khalitov
7
可以在这里看到可视化效果 https://codepen.io/chriscoyier/pen/vOZNQV - trungk18
有关节流和反弹的简单明了的JS实现,请参见我的回答中的Simple throttle in js - akinuri
2
我不太喜欢链接到个人网站,因为历史表明它们中的大多数迟早会下线。因此,我已经录制了nimus的去抖演示,以便如果将来它消失了,人们仍然可以在这里看到它:https://imgur.com/PkwDnfy - Advena
@thriqon:看到这个可视化后,几乎不需要再阅读其他答案了。谢谢。 - Mustafa Özçetin
27个回答

658
简单来说:
- **节流(Throttling)** 会延迟执行一个函数。它会减少多次触发的事件的通知次数。 - **防抖(Debouncing)** 会将一系列连续调用函数的操作合并为一次调用。它确保多次触发的事件只有一次通知。
你可以通过这个演示来直观地看到两者的区别在这个演示中,它展示了根据鼠标移动时防抖和节流事件触发的情况。
如果你有一个经常被调用的函数,比如在调整窗口大小或鼠标移动时,它可能会被多次调用。如果你不希望这种行为发生,你可以使用**节流**来以固定的时间间隔调用该函数。而**防抖**则意味着函数在一系列事件的结束(或开始)时被调用。

14
我认为thriqon的可视化链接非常清晰地展示了它的工作原理。如果一个函数被频繁调用,比如在发生调整大小或鼠标移动事件时,它可能会被调用很多次。如果你不想这样,你可以对它进行节流,使得函数以固定的时间间隔被调用。而防抖会使得函数在一系列调用结束(或开始)后被调用。 - Donal
23
@AdamM。请在此处查看可视化链接 - Donal
2
你一开始说..“节流会延迟执行函数。”,最后又说“防抖会延迟执行”,我以为我们在讨论它们的区别呢? - T J
6
@AdamM. 不行。你可以通过在demo中移动鼠标并不时停止鼠标移动来进行可视化。当你停止所有鼠标移动后,抖动栏将“滴答”一声响起,而节流栏将以减缓(节流)的速率继续“滴答” ,但在鼠标移动时仍会“滴答”。 - John Weisz
1
可视化链接已失效。这里是 Wayback Machine 的版本: https://web.archive.org/web/20220117092326/http://demo.nimius.net/debounce_throttle/。 - Adam Zerner
显示剩余6条评论

236

个人认为 debouncethrottle 更难理解。

因为这两个函数都有助于推迟和降低某些代码的执行频率。假设您正在反复调用由 throttle/debounce 返回的修饰函数......

  • Throttle: 在指定的时间段内,原始函数将最多被调用一次。
  • Debounce: 原始函数将在调用者停止在指定时间段内调用装饰函数后调用。

我发现 debounce 的最后部分很重要,可以理解它试图实现的目标。我还发现 _.debounce 的旧版本实现有助于理解(感谢https://davidwalsh.name/function-debounce)。

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function(func, wait, immediate) {
  var timeout;
  return function() {
    var context = this, args = arguments;
    var later = function() {
        timeout = null;
        if (!immediate) func.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
};

或许这个比喻有些牵强,但也许可以帮助理解。

假设你有一个名叫 Chatty 的朋友,她喜欢通过即时通讯和你聊天。假设每当她说话时,她会每隔5秒钟发送一条新的消息。当你的即时通讯应用程序图标上下跳动时,你可以采取以下三种方法:

  • 天真的方法:每当消息到达时都检查一遍。虽然不是最有效的方法,但你总是能及时了解到所有信息。
  • 节流方法:每5分钟检查一次(有新消息时)。当新消息到达时,如果在过去的5分钟内已经检查过了,则忽略它。这种方法可以节省时间,同时仍然保持联系。
  • 防抖方法:你认识 Chatty,她会把整个故事拆分成多个部分,一条接一条地发送。你等待直到 Chatty 讲完整个故事:如果她停止发送消息超过5分钟,你就会假定她已经讲完了,现在你来检查所有消息。

207

区别

+--------------+-------------------+-------------------+
|              |  Throttle 1 sec   |  Debounce 1 sec   |
+--------------+-------------------+-------------------+
| Delay        | no delay          | 1 sec delay       |
|              |                   |                   |
| Emits new if | last was emitted  | there is no input |
|              | before 1 sec      |  in last 1 sec    |
+--------------+-------------------+-------------------+

使用案例解释:

  • 搜索栏- 不想每次用户按键时都进行搜索? 想要在用户停止输入1秒后进行搜索。 在按键时使用1秒的防抖

  • 射击游戏-手枪每次射击需要1秒钟的时间,但用户会多次点击鼠标。 在鼠标点击上使用节流

反转它们的角色:

进一步解释输入输出与现实生活的比较

有一些保安在酒吧外面。 保安允许说“我会去”的人进入酒吧。 这是正常情况。 任何说“我会去”的人都可以进入酒吧。

现在有一个节流守卫节流5秒)。 他喜欢先回应的人。 第一个说出“我会去”的人,他就允许那个人进入。 然后,在5秒内,他拒绝每个人。 之后,再次有人首先说这句话,他就允许了,并且其他人将被拒绝5秒

还有另一个防抖守卫防抖5秒)。 他喜欢带给他5秒钟的心理休息的人。 所以如果有任何人说“我会去”,守卫会等待5秒钟。 如果在这5秒内没有其他人打扰他,他就允许第一个人进入。 如果另一个人在这5秒内说“我会去”,他将拒绝第一个人。 他会重新开始5秒的等待,以查看第二个人是否能为他带来心理休息。


2
非常清晰的解释! - Evgin
1
@Rivenfall Throttle和Debounce旨在减少噪音,因此它们被设计为会失去一些人。它们有不同的策略来失去人员。你要找的是延迟运算符[https://rxjs.dev/api/operators/delay]。它只会延迟他们的响应。Delay Gaurd将是普通的守卫,只是对于他来说,声音处理需要5秒钟。因此,在5秒的延迟后允许所有人。 - amitdigga
最终,我使用了一个日期差值 delta = 现在时间 - 上次更新时间。当 delta < 延迟时间时,使用 setTimeout 和延迟时间 delta。这样它会立即处理第一个事件,而不会忽略最后一个事件。 - Rivenfall
我误解了您的查询。您实际上是在说明不同的用例。 - amitdigga
1
这不太对。去抖动可以有或没有延迟。如果直接去抖动函数,就会有延迟。要想在没有延迟的情况下去抖动一个函数,请编写如果“flag”为真,则运行我的函数,然后将“flag”设置为假,然后去抖动一个语句将标志重新设置为真。也许除了“去抖动”之外还有其他词可以描述这个过程? - 75th Trombone
显示剩余2条评论

111

节流(1秒): 你好,我是一个机器人。只要你继续发送请求给我,我就会继续回复你,但是每次回复的时间间隔恰好为1秒。如果在一秒内再次发送请求给我,我仍会在恰好1秒的时间间隔后回复你。换句话说,我喜欢以精确的间隔回复。

去抖动(1秒): 嗨,我是那个 ^^ 机器人的表兄弟。只要你不断地发送请求给我,我将保持沉默,因为我喜欢在上一次收到请求后过了1秒才回复。我不知道这是因为我有态度问题还是我不想打断别人。换句话说,如果你在上次调用后的1秒内继续请求回复,你将永远得不到回复。是啊,呵呵...你可以称呼我为粗鲁。


节流(10分钟): 我是一个日志记录机器。我会在每隔10分钟定期向我们的后端服务器发送系统日志。

去抖(10秒):嗨,我不是那个日志记录机器的表兄弟。(在这个虚构的世界中,并非每个去抖器都与节流器有关系)。我在附近的餐馆做服务员。我应该告诉你,只要你继续添加订单,我就不会去厨房执行你的订单。只有在你最后修改订单后经过10秒钟,我才会认为你已经完成订单。然后我才会去厨房执行你的订单。


炫酷演示:https://css-tricks.com/debouncing-throttling-explained-examples/

服务员比喻来源:https://codeburst.io/throttling-and-debouncing-in-javascript-b01cad5c8edf


1
节流并不会自动延迟,只有在必要时才会这样做。 - trollkotze
1
非常好的解释。一分钟内澄清了。 - jyotishman saikia

60

节流(Throttling)强制规定了函数在一段时间内最多被调用的次数。例如,“每100毫秒最多只执行一次此函数”。

防抖(Debouncing)强制规定了函数必须在一定的时间内不被再次调用才能执行。例如,“只有在100毫秒内没有调用时才执行此函数”。

参考链接


39

比演示更简单。

当调用节流时,它会定期触发您的函数(例如,每20毫秒一次)- 并在最后一次触发时触发。

防抖只在最后触发(例如,最后一个事件之后的20毫秒)。

因此,只要事件继续快速触发(例如,间隔小于20毫秒),防抖将保持沉默,而节流将每20毫秒触发一次。两者都会在最后触发,区别只在于节流还会在设定的间隔内触发。

示例:如果您正在滚动,节流将在滚动时缓慢调用您的函数(每X毫秒一次)。防抖将等待您完成滚动后才调用您的函数(可能只调用一次)。


我喜欢把节流器想象成“包含去抖动”的方式,它们都会在事件完成后做出最终调用,但由于实现细节的差异,这两者并不总是在完全相同的时间做出最终调用,这会让演示变得混乱。

值得注意的是,在这些演示中,它们可能看起来不“完全相同”,因为防抖动将始终在最后一个事件之后X毫秒触发,而节流的最后一次调用可能会更早(并且不需要在防抖动通常触发时再次调用)。这对于查看演示来说并不重要,但值得一提。 - Ryan Taylor

30

用通俗易懂的语言来说:

防抖可以防止一个函数在频繁调用时重复运行。只有在确定该函数不再被调用后,它才会运行一次。防抖的实际例子包括:

  • 如果用户停止输入文本,则自动保存或验证文本框内容:操作只会在确定用户不再输入(不再按键)后运行一次。

  • 记录用户鼠标静止位置:用户不再移动鼠标,因此可以记录(最后的)鼠标位置。

节流将简单地防止函数在最近已运行过时再次运行,而不考虑调用频率。节流的实际例子包括:

  • v-sync的实现基于节流:仅当自上次屏幕绘制以来经过16ms时,才会绘制屏幕。无论刷新屏幕功能被调用多少次,它最多每16ms运行一次。

28

一个我个人觉得很有帮助的现实生活比喻:

  • debounce = 一次对话。你等待另一个人说完才回应。
  • throttle = 鼓点。你只在简单的4/4鼓点上演奏音符。

debounce 的使用场景:

  • 打字。你想要在用户停止输入后做一些事情。因此,在最后一次按键之后等待1秒是有意义的。每次按键都会重新开始等待。
  • 动画。你想要在用户停止悬停在元素上后将其缩小。不使用 debounce 可能会导致动画出现问题,因为光标意外移动到“热”和“冷”区域之间。

throttle 的使用场景:

  • 滚动。你想要对滚动做出反应,但要限制计算量,所以每100毫秒做一次处理足以防止潜在的延迟。
  • 鼠标移动。与滚动相同,但是针对鼠标移动。
  • API 调用。你想要在某些 UI 事件上触发 API 调用,但想限制你所做的 API 调用次数,以防止服务器过载。

24

一图胜千言

流量控制 流量控制

去抖动 去抖动

注意去抖动直到事件流停止后才会触发,然而流量控制会在每个间隔期内触发一个事件。

(感谢CSS-Tricks)


实时代码示例:https://codepen.io/dcorb/pen/KVxGqN - Gil Epshtain
7
如果原始事件在油门和去抖图像上都匹配,我无法想象这些话语会有多少更大的价值... - ocodo

23

去抖动(Debouncing)允许您管理函数可以接收的调用频率。它组合了在给定函数上发生的多个调用,以便在特定时间段到期之前发生重复调用时被忽略。基本上,去抖动确保为可能多次发生的事件只发送一个信号。

节流(Throttling)限制了函数接收调用的频率至固定的时间间隔内。它用于确保目标函数不会比指定延迟更频繁地调用。节流是重复事件速率的减少。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接