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

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个回答

11
将防抖和节流结合在一起可能会非常令人困惑,因为它们都共享一个名为delay的参数。

enter image description here

防抖。延迟是等待直到没有更多的调用,然后再进行调用。就像关闭电梯门一样:门必须等待没有人试图进入才能关闭。
节流。延迟是等待一定的频率,然后对最后一个进行调用。就像开枪射击一样,枪不能以超过一定的速率射击。
让我们来看一下具体的实施细节。
function debounce(fn, delay) {
  let handle = null
  
  return function () {
    if (handle) {
      handle = clearTimeout(handle)
    }
    
    handle = setTimeout(() => {
      fn(...arguments)
    }, delay)
  }
}

Debounce,会在超时期间不断中断,直到没有更多的中断,然后触发fn
function throttle(fn, delay) {
  let handle = null
  let prevArgs = undefined
  
  return function() {
    prevArgs = arguments
    if (!handle) {
      fn(...prevArgs)
      prevArgs = null
      handle = setInterval(() => {
        if (!prevArgs) {
          handle = clearInterval(handle)
        } else {
          fn(...prevArgs)
          prevArgs = null
        }
      }, delay)
    }
  }
}

节流函数(Throttle)会存储最后一次调用的参数,并设置一个间隔,直到不再有过去的调用。
相似之处:它们都有延迟时间,在延迟期间不会触发事件,尤其是只有一个事件时。两者都不会聚合过去的事件,因此事件数量可能与实际触发次数不同。
不同之处:在防抖动(Debounce)情况下,延迟时间可以通过重复事件进行延长。而在节流情况下,延迟时间是固定的。因此,通常情况下,节流会比防抖动触发更多的事件。
易于记忆:防抖动将多个调用分组为一个调用。节流将调用限制在一定的频率内。
更新于1-20-23
节流函数可能不需要使用setInterval,以下是我最近编写的一个新版本,它还处理了this的问题。
function throttle(fn, delay) {
  let canFire = true
  let queue = []

  function pop() {
    if (queue.length < 1) return 

    const [that, args] = queue.pop()
    fn.apply(that, args)
    canFire = false
    setTimeout(() => {
      canFire = true
      pop()
    }, delay)
  }
  
  function push() {
    queue.push([this, arguments])
    if (canFire) pop()
  } 

  push.cancel = () => {
    queue = []
  }

  return push
}

更新于11-09-23

我开始相信节流是在防抖的基础上的一个附加功能。阅读这篇文章https://windmaomao.medium.com/throttle-is-a-debounce-add-on-80d4a6027ad4

function throttle(fn, delay) {
  let h
  let queue = []

  function pop() {
    if (queue.length < 1) return 

    if (!h) {
      const [that, args] = queue.pop()
      fn.apply(that, args) 
      
      h = setTimeout(() => {
        h = null
        pop()
      }, delay)      
    }      
  }
  
  return function push() {
    queue.push([this, arguments])
    pop()
  } 
}

9

限流

限流是指在一段时间内,对某个函数的调用次数进行最大限制。比如“每100毫秒最多执行一次该函数”。假设在正常情况下,在10秒内你会调用该函数1000次。如果将该函数的限流设为每100毫秒最多执行一次,则该函数最多只会被执行100次。

(10s * 1,000) = 10,000ms
10,000ms / 100ms throttling = 100 maximum calls

防抖

防抖确保一个函数在没有被调用的情况下经过一定时间后才能再次调用。比如说,"只有在100毫秒内没有被调用时才执行该函数"。

假设一个函数在3秒钟内以快速方式连续调用了1,000次,然后停止调用。如果你在100毫秒内进行了防抖处理,那么该函数将在3.1秒时执行一次,即整个突发调用结束后。每当在连续调用期间重新调用该函数时,它都会重置防抖计时器。

来源:- 节流和防抖的区别


5

假设我们有一个回调函数"cb",要在事件"E"上调用它。

如果事件“E”在1秒内触发了1000次,那么就会有1000次对“cb”的调用。也就是说,每毫秒调用一次。为了优化,我们可以使用以下两种方法之一:

  • 节流:使用(100ms)的节流,"cb"将在[100ms、200ms、300ms、...1000ms]上被调用。也就是说,每100ms调用一次。“这里的1000次对“cb”的调用被优化为10次。”
  • 防抖:使用(100ms)的防抖,“cb”仅在[1100ms]时被调用一次。也就是说,在最后一次触发“E”之后的100ms内调用。“这里的1000次对“cb”的调用被优化为1次。”

5

throttledebounce的一个封装,它使得debounce在一定时间内调用传递过来的function。如果debounce延迟了一个比指定的时间更长的函数调用周期。


4

去抖动节流是从一系列事件中选择目标以进行减少的方法。它们都需要一个时间段作为参数,例如:x毫秒,并且需要leading / tailing变量来定义如何选择事件。

去抖动

当在接下来的x毫秒内没有其他事件发生时,选择一个事件。

"--->": timeline

"o,c,e": events, where 
"o" are events selected by "leading"
"e" are events selected by "tailing"
"c" are skipped events

"|===|": period (x=5)

--oc-e-----occ-e----o-cc--ce-----o-c-cce------> source events
  |===|    |===|    |===|        |===|
  ||===|   ||===|   | |===|      | |===|   
  |  |===| | |===|  |  |===|     |   |===|
  |      | |   |===||     |===|  |    |===|
  |      | |       ||      |===| |     |===|
--o--------o--------o------------o------------> selected events (leading)
---------e---------e-----------e-----------e--> selected events (tailing)

流量控制

每当事件发生时,在每 x 毫秒内选择一个事件

--oc-e-----occ-e----o-ce--oe-----o-c-eoe------> source events
  |===|    |===|    |===| |===|  |===||===|
--o--------o--------o-----o------o----o-------> selected events (leading)
------e--------e--------e-----e------e----e---> selected events (tailing)

4

2

硬件中的去抖动原理。

去抖动是从数字信号中去除噪音的过程。当按下按钮时,信号的反弹会导致按钮被注册为被按下多次。去抖动可消除此噪音,使按钮只被注册为被按下一次。 想象一根尺子在桌子边缘上弹跳,想象开关内部的金属触点也像这样弹跳。

更好的是,看一下这个图示,展示了由于弹跳而引起的开关噪音。

我们使用适当计算等级的电阻器和电容器来平滑信号,使其持续n毫秒。

解释硬件中信号节流的工作原理。

信号节流是限制信号被注册次数的过程。这通常用于防止按钮在短时间内被注册为多次按下。
我更喜欢术语“门控”,但那是因为我对电子音乐制作感兴趣。
我们在每个节流周期结束时打开门,允许信号通过,然后再关闭门,进行下一个节流周期。

解释软件中去抖动的工作原理。

软件中的去抖动通常是通过使用定时器来实现的。当按下按钮时,定时器会启动。如果在定时器到期之前再次按下按钮,则会重置定时器。这确保了按钮在去抖动周期内只能被注册为一次按下。
在许多去抖动的实现中,我们会创建一个包含定时器(或门)的闭包,其中嵌入了函数的去抖版本。当定时器延迟到期时,我们将其设置为 null。只有当定时器为 null 时,实际函数才会运行。通常,这意味着当我们首次调用去抖函数时,它将运行一次,然后对它的后续调用将被有效地取消,直到延迟时间已过去。
在某些去抖实现中,当一系列调用被触发且计时器尚未过期时,计时器将被重新启动。只有在弹跳停止后才调用该函数。这通常称为尾随去抖。
软件中的限流通常通过使用计数器来实现。当按下按钮时,计数器会递增。如果在计数器达到某个阈值之前再次按下按钮,则计数器将被重置。这限制了在给定时间内可以注册为按下的按钮次数。最好将此视为一个脉冲或节拍,它在发送调用到限流时打开和关闭门。
速率限制是另一种思考限流的方式。
为什么这是一个常见的混淆原因?
在许多情况下,使用防抖或节流可以帮助您获得想要的结果,特别是如果您正在使用的软件实现允许您链接、跟踪或引导您的节流/防抖。尽管如此,我希望这里所有的答案以及这个答案都能帮助您更清楚地了解它们之间的区别。它们非常相似。

2

防抖使得一个函数只能在自上次被调用后的一定时间间隔后才能执行。

function debounce(func,wait){
  let timeout
  return(...arg) =>{
    clearTimeout(timeout);
    timeout= setTimeout(()=>func.apply(this,arg),wait)
  }
}


function SayHello(){
  console.log("Jesus is saying hello!!")
}


let x = debounce(SayHello,3000)
x()
设计模式限制了在一段时间内调用给定事件处理程序的最大次数。它允许处理程序周期性地按指定间隔调用,忽略在等待期结束之前发生的每个调用。
function throttle(callback, interval) {
  let enableCall = true;

  return (...args)=> {
    if (!enableCall) return;

    enableCall = false;
    callback.apply(this, args);
    setTimeout(() => enableCall = true, interval);
  }
}


function helloFromThrottle(){
  console.log("Jesus is saying hi!!!")
}

const foo = throttle(helloFromThrottle,5000)
foo()

不要在 callback.apply() 中使用 this,也不要返回箭头函数。 - Bergi

2

enter image description here

防抖和节流在Javascript中的应用
这两个函数都有助于延迟或限制某些函数的执行速率,从而优化我们的Web应用程序。
什么是节流?
- 节流是指在给定的时间内触发函数的方式。 - 第一个请求被允许通过。 - 原始函数每隔指定的时间只执行一次。
示例: 下面的图像描述了以40毫秒的间隔/延迟触发的点击事件, 事件e2、e3和e4在40毫秒内发生,因此没有被执行。

enter image description here

限流代码,

const trottle = function (fn, d) {
  let flag = true;        // to pass the first event

  return function () {
    if (flag) {
      fn();              // execute the decorative function
      flag = false;      // block the next fired events
      setTimeout(() => {
        flag = true;     // unblock the next fired events after a specified delay
      }, d);
    }
  };
};

节流函数通常用于调整大小或滚动事件。

什么是防抖?

  • 防抖是一种方式,可以在最近被调用时阻止函数的调用。
  • 只允许最后一个请求通过。
  • 只有在调用者停止对装饰函数的连续调用一定时间后,才执行原始函数。

示例:下面的图片描述了只有在完成延迟(100毫秒)后才会触发点击事件。

enter image description here

用户触发了事件e1、e2、e3和e4,但由于连续事件发生在100毫秒内,所以它们没有成功执行。(例如e1和e2之间的延迟小于100毫秒,或者e2和e3之间的延迟小于100毫秒...等等) 事件e4成功调用了装饰函数,因为用户触发后经过了100毫秒的延迟。 防抖代码:

const debounce = function (fn, d) {
  let timer;

  return function () {
    clearTimeout(timer);           // clears/reset the timer if the event is triggered withing the delay
    timer = setTimeout(() => {     // execute the function
      fn();
    }, d);
  };
};

防抖动函数通常用于限制击键(输入字段)或点击事件。
防抖和节流的实际应用:

enter image description here

抨击与限制 你可以在这里直观地看到区别
支持文件:

https://www.geeksforgeeks.org/javascript-throttling/ https://www.geeksforgeeks.org/debouncing-in-javascript/

节流和防抖函数的区别

https://www.youtube.com/watch?v=TBIEArmPywU


1
据我所了解,简单来说: 限流(throttling)- 类似于对某个事件的发生调用 setInterval(callback),即在一定时间内多次调用同一个函数。 防抖(debouncing)- 类似于在某个事件的发生后等待一段时间后调用 setTImeout(callbackForApi) 或调用某个函数。 这个链接可能会有帮助- https://css-tricks.com/the-difference-between-throttling-and-debouncing/

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