I. Preface:

In our daily development, there will be many scenarios that trigger events frequently, such as the search box real-time request, onMousemove, Resize, onScroll and so on. Sometimes, we can not or do not want to trigger events frequently, how to do? This time should use function tremble and function throttling!


Let’s start with an example demo entry

<div id="content" style="height:150px; line-height:150px; text-align:center; color: #fff; background-color:#ccc; font-size:80px;"></div>
 
<script>
  let num = 1;
  let content = document.getElementById('content');

  var lastDate = new Date().getTime();

  function count() {
      content.innerHTML = num++;

      var nowDate = new Date().getTime();
      // Output the time difference between two executions
      console.log(nowDate-lastDate)
      lastDate = nowDate
  };
  content.onmousemove = count;
</script>
Copy the code

This code, moving the mouse over the gray area, will continue to trigger the count() function, resulting in the following effect:

The count() function is printed by the console as follows:


Next we limit the frequency of operation by anti-shake and throttling.

2, function anti-shock (debounce)

If the same event is triggered several times within a short period of time, only the last (not immediate) or the first (immediate) event is executed. The intermediate event is not executed.

【 Concept 】 Execute the callback after the event is triggered for n seconds. If the event is triggered again within n seconds, the timer is reset.

If someone enters the elevator (triggers the event), the elevator will leave in 10 seconds (executes the event listener), and if someone enters the elevator again (triggers the event again in 10 seconds), we will have to wait another 10 seconds to leave (re-timing).

There are two versions: non-immediate and immediate. The demo entry

2.1 Non-immediate execution (delayed execution)

// Non-immediate version (delayed execution)
function debounce(func, wait) {
  var timer;
  return function() {
    var context = this; // Note that this points to
    var args = arguments; // Arguments holds event

    if (timer) clearTimeout(timer);

    timer = setTimeout(function() {
      func.apply(this, args)
    }, wait)
  }
}

// Use it as follows:
content.onmousemove = debounce(count,1000);
Copy the code

The non-immediate version means that the function is not executed immediately after the event is fired, but n seconds later. If the event is fired within n seconds, the function execution time is recalculated. It will only be executed if no more events are raised for the next n seconds. The result is as follows: Demo entry

After “function stabilization” processing, the console output count() function execution interval is as follows:

You can see that the execution interval is larger, which reduces the frequency of the function execution, thus optimizing the performance.


Use event listening (sometimes required to bind and unbind events, as in Vue and React)

// Re-name a buffered function
var handleMousemove = debounce(count,1000)

/ / binding
window.addEventListener('mousemove', handleMousemove)

/ / unbundling
window.removeEventListener('mousemove', handleMousemove)
Copy the code


2.2. Execute now

// Execute now version
function debounce(func, wait) {
  var timer;
  return function() {
    var context = this; // Note that this points to
    var args = arguments; // Arguments holds event

    if (timer) clearTimeout(timer);

    varcallNow = ! timer; timer =setTimeout(function() {
      // If the callNow is set to null, the next callNow will be true.
      timer = null;
    }, wait)

    if(callNow) func.apply(context, args); }}// Use it as follows:
content.onmousemove = debounce(count,1000);
Copy the code

The immediate execution version means that the function is executed immediately after the event is fired, and then the effect of the function cannot be continued until the event is fired for n seconds. As above, the effect is as follows:

The demo entry


2.3 synthetic version

/ / synthetic version
/ * * *@desc Function stabilization *@param Func target function *@param Number of milliseconds to delay wait execution *@param Immediate true - Immediate execution, false - Delay execution */

function debounce(func, wait=16, immediate) {
  var timer;
  return function() {
    var context = this; // Note that this points to
    var args = arguments; // Arguments holds event
          
    if (timer) clearTimeout(timer);
    if (immediate) {
      varcallNow = ! timer; timer =setTimeout(function() {
        // If the callNow is set to null, the next callNow will be true.
        timer = null;
      }, wait);
      if (callNow) func.apply(context, args);
    } else {
      timer  = setTimeout(function() {
        func.apply(context, args);
      }, wait)
    }
  }
}
Copy the code


Function throttle

Events are fired continuously but the function is executed only once in n seconds. That is, two executions within 2n seconds… . Throttling literally dilutes the execution frequency of the function.

Concept: Specifies a unit of time. Within this unit of time, only one callback function that triggers an event can be executed. If an event is triggered multiple times within the same unit of time, only one callback function can take effect.

One theory is that when more than 24 pictures are played continuously in one second, a continuous animation will be formed in the eyes of human eyes, so in the movie playing (used to be, but now is not known), it is basically played at the speed of 24 pictures per second. Why not 100 or more is because 100 would be a waste of resources at a time when 24 is enough for human vision.

There are also two versions, the immediate execution version (timestamp version) and the delayed execution version (timer version). The demo entry

3.1 Immediate Execution Version (timestamp version)

// Execute now (timestamp version)
function throttle(func, wait) {
  var previous = 0;
  return function() {
    var now = Date.now();
    var context = this; // Note that this points to
    var args = arguments; // Arguments holds event
    if(now - previous > wait) { func.apply(context, args); previous = now; }}}// Use it as follows:
content.onmousemove = throttle(count,1000);
Copy the code

The result is as follows: Demo entry

As you can see, the function is executed immediately and every 1s during a continuous event firing.

After “function stabilization” processing, the console output count() function execution interval is as follows:

You can see that the execution frequency is approximately 1000ms, but both are greater than 1000ms. To achieve the specified frequency and throttling effect.


3.2 Delay Execution Version (Timer version)

// Delay execution (timer version)
function throttle(func, wait) {
  var timer;
  return function() {
    var context = this; // Note that this points to
    var args = arguments; // Arguments holds event
    if(! timer) { timer =setTimeout(() = > {
        timer = null;
        func.apply(context, args)
      }, wait)
    }
  }
}

// Use it as follows:
content.onmousemove = throttle(count,1000);
Copy the code

Use the same, the effect is as follows: Demo entry

As you can see, the function is not executed immediately during a continuous event firing, and is executed every 1s, and again after the event firing is stopped.

It should be easy to see that the difference between the timestamp and timer throttling functions is that the timestamp function fires at the “start” of the period, while the timer function fires at the “end” of the period.


Similarly, we can combine the timestamp and timer throttling functions to implement the synthetic throttling functions.

3.3 synthetic version

/ / synthetic version
/ * * *@desc Function throttling@param Func function *@param Number of milliseconds to delay wait execution *@param Immediate True - Execute immediately (timestamp version), false - delay execution (timer version) */
function throttle(func, wait, immediate) {
  if (immediate) {
    var previous = 0;
  } else {
    var timer;
  }
  return function() {
    var context = this; // Note that this points to
    var args = arguments; // Arguments holds event
    if (immediate) {
      var now = Date.now();

      if(now - previous > wait) { func.apply(context, args); previous = now; }}else {
      if(! timer) { timer =setTimeout(() = > {
          timer = null;
          func.apply(context, args)
        }, wait)
      }
    }
  }
}
Copy the code


4. The application scenarios of function anti-shake and function throttling respectively

4.1. There are the following application scenarios for function shaking prevention:

  1. When AJAX validation is performed on continuous input to the input box, using function stabilization can effectively reduce the number of requests.
  2. Check whether the scroll slides to the bottom.
  3. Add functions to buttons to prevent multiple forms from being submitted.

In general, it is suitable for multiple events with one response

4.2 For function throttling, there are the following scenarios:

  1. DOM element drag and drop
  2. Canvas function
  3. Refresh rate in the game
  4. Onmousemove, resize, onScroll, etc

In general, it is suitable for a large number of events to be triggered evenly by time.


V. Appendix:

5.1 About “This pointing” and “arguments” in throttling/anti-shake function

Let’s examine the following code

function throttle(func, wait) {
  var timer;
  return function() {
    var context = this; // Note that this points to
    var args = arguments; // Arguments holds event
    if(! timer) { timer =setTimeout(() = > {
        timer = null;
        func.apply(context, args)
      }, wait)
    }
  }
}

// Use it as follows:
content.onmousemove = throttle(count,1000);
Copy the code

Throttle (count,1000) returns a new anonymous function, so content.onMousemove = throttle(count,1000)

content.onmousemove = function() {
  var context = this; // Note that this points to
  var args = arguments; // Arguments holds event
  
  // var args = Array.prototype.slice.call(arguments, 1); // This removes events from arguments
  
  console.log('this'.this); // Outputs the contentDOM element
  console.log('arguments'.arguments); // Outputs an array of events
  
  if(! timer) { timer =setTimeout(() = > {
      timer = null;
      func.apply(context, args)
    }, wait)
  }
}
Copy the code

So far, the event function has only been bound and has not yet been executed, and the direction of this will not be determined until it is actually run.

Second, the above anonymous function, Content.onMousemove (), is actually executed when the onMousemove event is triggered. In this case, the above anonymous function is executed through the object. Function name (), so this inside the function naturally refers to the object (content).

Finally, if func calls inside anonymous functions are the most common way to call func(), then this inside func must point to the window, which would be a hidden bug! So, we capture this with an anonymous function, and then modify the this reference with func.apply().


5.2 optimal time interval for function throttling

In all of the examples above, we used a 1000ms interval (1 second), but in practice, a second is too slow for DOM element drag and drop or page scroll to affect the user experience. However, if the time interval is too short, it loses the significance of throttling. How much time interval should we set?

Let’s look at another example of a page scroll event.

<script>
var num = 1;
var content = document.getElementById('content');
var lastDate = new Date().getTime();

function count() {
  content.innerHTML = num++;

  var nowDate = new Date().getTime();
  // Output the time difference between two executions
  console.log(nowDate-lastDate)
  lastDate = nowDate
};


window.onscroll = throttle(count, 10);
Copy the code

We set the throttling time interval to 10ms, and the minimum output time interval should be 10. But the implementation output is almost always at least 16. The demo entry

Mouse movement events occur at very small intervals. (Below is the output of the mouse movement event in the above example without shaking and throttling)

Why is that?

Before we talk about why, let’s talk about another thing — monitor refresh rate. A typical monitor refreshes at 60Hz, and so does mine. This 60Hz means the frequency of screen refresh in 1 second. So you can calculate that the interval between each refresh of a 60Hz monitor is 1000/60 (approximately equal to 16).

If we change the display refresh frequency, such as 48Hz. Each refresh interval is 1000/48 (approximately 20).

The output result is shown as follows:

Therefore, the minimum interval of function execution during the scrolling event is determined by the refresh frequency of the display. Most monitors refresh at 60Hz, so the minimum interval is generally 16ms. Some friends playing games monitor may be higher requirements, refresh frequency is also higher.

Therefore, if it is a mouse movement event, even if the triggering interval is less than 16ms, the user will only see the fastest 16ms. This will cause some of the calculation waste. Therefore, the maximum time interval for function throttling should be set to 16ms. If the amount of calculation is too large and the performance is affected, you can adjust the value appropriately. To ensure that user experience is not affected, the recommended value is less than 30ms.


Source code example

  1. Javascript function debounce source code example
  2. Javascript functions are throttle source code examples
  3. Function throttling optimal time interval test

Further reading

  1. Js functions debounce and Throttle
  2. Detailed solution of JS function coriolization
  3. JS function tremble and function throttling

The article source

Javascript function debounce and throttle details – Yang’s personal website