The principle and implementation of Debounce and Throttle—preventing an event from being triggered frequently

Original text: http://blog.csdn.net/redtopic/article/details/69396722

When handling events such as resize, scroll, mousemove and keydown/keyup/keypress, usually we do not It is expected that these events will be triggered too frequently, especially if the listener involves a lot of calculations or has very resource-intensive operations.

How often? Take mousemove as an example. According to the regulations of DOM Level 3, “If the mouse moves continuously, the browser should trigger multiple consecutive mousemove events.” This means that browsing The controller fires the mousemove event based on how fast the user moves the mouse, if its internal timer allows it. (Of course, if you move the mouse fast enough, such as “swiping” it, the browser will not trigger this event). Events such as resize, scroll, and key* are similar.

You can refer to this Demo to experience it.

Debounce

The concept of debounce in DOM events is actually derived from the “debounce” of mechanical switches and relays. The basic idea is to merge multiple signals into one signal. This article explains it very clearly. If you are interested, you can read it.

In JavaScript, what the debounce function does is to force a function to be executed only once within a certain continuous period of time, even if it would have been called multiple times. We hope that the corresponding listening function will be executed after the user stops an operation for a period of time, instead of executing the listening function as many times as the browser triggers the event during the user’s operation.

For example, if the mouse is continuously moved within a certain period of 3 seconds, the browser may trigger dozens (or even hundreds) of mousemove events. If debounce is not used, , the listening function will be executed so many times; if a 100ms “de-bounce” is used for the listening function, the browser will only execute the listening function once, and it will be executed at the 3.1s.

Now, let’s implement a debounce function.

Implementation

Our debounce function receives two parameters. The first is the callback function fn to “debounce”, and the second is the delay time delay.

In fact, most complete debounce implementations also have a third parameter immediate, indicating that the callback function is executed at the beginning of a time interval (immediate code> is true) or is executed last (immediate is false), such as underscore’s _.debounce. This article does not consider this parameter, only the final execution situation. Those who are interested can study it by themselves.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
twenty one
twenty two
twenty three
twenty four
25
26
27
28
29
/**
*
* @param fn {Function} The actual function to be executed
* @param delay {Number} Delay time, that is, threshold, unit is milliseconds (ms)
*
* @return {Function} returns a "de-bounced" function
*/
function debounce(fn, delay) {

  //Timer, used to setTimeout
  var timer

  // Return a function that will execute the fn function delay milliseconds after the end of a time interval
  return function () {

    //Save the context and parameters when calling the function and pass them to fn
    var context = this
    var args = arguments

    // Each time this returned function is called, clear the timer to ensure that fn is not executed
    clearTimeout(timer)

    // When the returned function is called for the last time (that is, the user stops a continuous operation),
    // Execute fn after delay milliseconds
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

In fact, the idea is very simple. debounce returns a closure. This closure will still be called continuously and frequently, but inside the closure, the original function fn is restricted. Execute, forcing fn to be executed only once after continuous operation has stopped.

debounce is used as follows:

1
2
3
$(document).on('mouvemove', debounce(function(e) {
// code
}, 250))

Use cases

Or take mousemove as an example, and bind a “de-bounce” listener to it. What is the effect? Please take a look at this demo.

Let’s consider another scenario: sending an ajax request to the server to obtain data in real time based on user input. We know that the browser triggers key* events very quickly. Even at the normal typing speed of normal people, the frequency of key* events being triggered is very high. By sending requests at this frequency, firstly, we do not get the complete input from the user and send it to the server, and secondly, such frequent useless requests are really unnecessary.

A more reasonable approach is to wait for the user to “stop” typing for a short period of time before sending the request. Then debounce comes in handy:

1
2
3
$('input').on('keyup', debounce(function(e) {
//Send ajax request
}, 300))

You can check out this Demo to see the effect.

Throttle

The concept of throttle is easier to understand. It is a fixed function execution rate, which is the so-called “throttle”. Under normal circumstances, the listening function of mousemove may be executed every 20ms (assuming). If a “throttle” of 200ms is set, then it will be executed every 200ms. For example, in a 1s period, the normal listening function may be executed 50 (1000/20) times, and after “throttling” for 200ms, it will be executed 5 (1000/200) times.

Let’s look at the Demo first. It can be seen that no matter whether the mouse movement speed is slow or fast, the “throttling” listening function will be executed every 250ms at a “constant speed”.

Implementation

Similar to debounce, our throttle also receives two parameters, a function to be executed fn and an execution interval threshold threshhold .

Similarly, a more complete implementation of throttle can be found in underscore’s _.throttle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
twenty one
twenty two
twenty three
twenty four
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
*
* @param fn {Function} The actual function to be executed
* @param delay {Number} execution interval, unit is milliseconds (ms)
*
* @return {Function} returns a "throttling" function
*/

function throttle(fn, threshold) {

  //Record the time of last execution
  var last

  // timer
  var timer

  //The default interval is 250ms
  thresholdhold || (threshhold = 250)

  //The returned function executes the fn function every threshhold milliseconds
  return function () {

    //Save the context and parameters when the function is called and pass it to fn
    var context = this
    var args = arguments

    var now = + new Date()

    // If the time since the last execution of the fn function is less than threshold, then give up
    //Execute fn and retime
    if (last & amp; & amp; now < last + threshold) {
      clearTimeout(timer)

      // Ensure that fn is executed again after the current time interval ends
      timer = setTimeout(function () {
        last = now
        fn.apply(context, args)
      }, threshold)

    // Execute fn once at the beginning of the time interval and when the specified interval is reached
    } else {
      last = now
      fn.apply(context, args)
    }
  }
}

The principle is not complicated. Compared with debounce, it only has one more time interval to judge, and the other logic is basically the same. throttle is used as follows:

1
2
3
$(document).on('mouvemove', throttle(function(e) {
// code
}, 250))

Use cases

A common scenario for throttle is to limit the triggering frequency of resize and scroll. Take scroll as an example, check out this Demo to get a feel for it.

Visual explanation

If you still can’t fully understand the difference between debounce and throttle, you can go to this page to see a visual comparison of the two.

Picture

Summary

debounce forces the function to execute only once within a certain period of time, and throttle forces the function to execute at a fixed rate. They can greatly improve the user experience when dealing with some frequently triggered DOM events.