This article appears on Github Blog.

Both anti-shake and throttling are used in scenarios where high-frequency events are triggered, such as scroll and input events. The core realization idea of anti-shake and throttling is to add a control layer between events and functions to achieve the function of delayed execution. The purpose is to prevent frequent execution of some operations within a certain period of time, resulting in resource waste.

The control layer between events and functions usually has two ways to achieve: one is the use of timer, each time the event is triggered to determine whether there is a timer, is the way we achieve this article. The other is to record the timestamp triggered by the last event. When each event is triggered, a difference between the current timestamp and the last timestamp is judged (deplay – (now-previous)), whether the set delay time is reached.

Visual effect comparison

The diagram below shows a comparison of mouse-moving events with throttle and debounce_throttle, a visual tool.

Debounce

The anti-shake function is executed after the event touches the specified time. If the event is triggered again within the specified time, the timer will be reset according to the last time.

Example of life scene: When the bus arrives at the stop, the bus driver will not close the door immediately after getting on a person, but will wait for the last person to get on or the bus is full before closing the door and starting.

Associative input – General example

For example, the search box suggests that when we input data, we may request the interface to obtain data. If we do not do any processing, interface requests will be constantly triggered at the beginning of the input, which is bound to cause a waste of resources. It is also undesirable to operate DOM frequently.

// Bad code
<html>
  <body>
    <div> search: <input id="search" type="text"> </div>
    <script>
      const searchInput = document.getElementById("search");
      searchInput.addEventListener('input', ajax);
      function ajax(e) { // simulate data query
        console.log(`Search data: ${e.target.value}`);
      }
    </script>
  </body>
</html>
Copy the code

We haven’t done any optimization on the above code, using the Ajax () method to simulate the data request, so let’s see how it works.

If it is a real interface that is called, the server interface will be used continuously from the moment it is entered, wasting unnecessary performance. It is also easy to trigger traffic limiting measures of the interface, such as the maximum number of requests per hour provided by Github API.

Associative input – Shake proof example

Let’s implement an anti-shock function (**debounce****) ** optimize the above code. The principle of ** is to judge whether there are multiple calls within the specified time by marking. When there are multiple calls, clear the last timer and restart the timer. If there is no call again within the specified time, the incoming callback function ****fn** will be executed.

function debounce(fn, ms) {
  let timerId;

  return (. args) = > {
    if (timerId) {
      clearTimeout(timerId);
    }

    timerId = setTimeout(() = > {
      fn(...args);
    }, ms);
  }
}
Copy the code

This is appropriate for search scenarios, where we want to modify the original associative input example based on the last input.

const handleSearchRequest = debounce(ajax, 500)
searchInput.addEventListener('input', handleSearchRequest);
Copy the code

This time it is much better to use the last input interface as the request interface when continuous input pauses, so as to avoid constantly refreshing the interface.

Remember to clear events when appropriate. For example, in React, we listen for input when a component is mounted, and clear the corresponding event listener function when a component is unmounted.

componentDidMount() {
  this.handleSearchRequest = debounce(ajax, 500)
	searchInput.addEventListener('input'.this.handleSearchRequest);
}

componentWillUnmount() {
  searchInput.removeEventListener('input'.this.handleSearchRequest);
}

Copy the code

Throttle

Throttling is the execution of a callback function at a specified interval after an event is triggered.

Example of life scenario: When we take the subway, the train always runs at the designated interval of 5 minutes (maybe other time), when the time is up, the train will leave.

Scroll to top – General example

For example, there are many list items on the page. When we scroll down, we hope to see a Top button and click it to return to the Top. At this time, we need to obtain the distance between the scroll position and the Top to determine whether to display the Top button.

<body>
  <div id="container"></div>
  <script>
    const container = document.getElementById('container');
    window.addEventListener('scroll', handleScrollTop);
    function handleScrollTop() {
      console.log('scrollTop: '.document.body.scrollTop);
      if (document.body.scrollTop > 400) {
        // Handle the display button operation
      } else {
        // Handle not showing button operations}}</script>
</body>
Copy the code

As you can see, if no processing is added, one scroll can trigger hundreds of times, which is obviously a waste of performance.

Scroll to the top – Throttling processing example

Implement a simple throttling function, similar to the anti-shock function, except that the flag bit is used to determine whether the request has been triggered. When it has been triggered, the incoming request is terminated until the last specified interval has been reached and the callback function has been executed before the next processing.

function throttle(fn, ms) {
  let flag = false;
  return (. args) = > {
    if (flag) return;
    flag = true;
    setTimeout(() = >{ fn(... args) flag =false; }, ms); }}Copy the code

Modify the above example to see the result.

const handleScrollTop = throttle(() = > {
  console.log('scrollTop: '.document.body.scrollTop);
  // todo:
}, 500);
window.addEventListener('scroll', handleScrollTop);
Copy the code

This is much better than the “regular scroll to the top example” above.

Remember to clear events

Take React as an example. When a component is mounted, we listen for the Scroll event of the window. When the component is unmounted, we remember to remove the corresponding event listener function. If you forget to remove the component when uninstalling it, the ScrollTop component was introduced on page A originally. After the single-page application jumps to page B, the ScrollTop component is not introduced on page B, but it will also be affected, because the event has been registered in the Window global object. There are also memory leaks.

class ScrollTop extends PureComponent {
  componentDidMount() {
    this.handleScrollTop = throttle(this.props.updateScrollTopValue, 500);
    window.addEventListener('scroll'.this.handleScrollTop);
  }

  componentWillUnmount() {
    window.removeEventListener('scroll'.this.handleScrollTop);
  }
  
  // ...
}
Copy the code

requestAnimationFrame

RequestAnimationFrame is an API provided by the browser that tells the browser that I need to run an animation. This method requires the browser to call the specified callback function to update the animation before the next redraw. This API is covered in the JavaScript Asynchronous Programming guide explore event loops in the browser.

It is affected by the browser’s refresh rate, which is 16.67ms for 60fps, and not multiple renderings for 16.67ms for multiple DOM operations.

This is equivalent to throttle(fn, 16.67) when the browser refreshes at 60fps. It needs to be handled before it is used. It cannot be executed immediately. It is triggered by an event.

const handleScrollTop = () = > requestAnimationFrame(() = > {
  console.log('scrollTop: '.document.body.scrollTop);
  // todo:
});
window.addEventListener('scroll', handleScrollTop);
Copy the code

RequestAnimationFrame this is a browser API and is not supported in Node.js.

Community toolset support

Some JavaScript toolset frameworks in the community, such as Underscorejs and LoDash, also provide support for anti-shock and throttling.

As mentioned at the beginning, another implementation method is to record the timestamp triggered by the last event. When each event is triggered, the difference between the current timestamp and the timestamp executed last time is determined to determine whether the set delay time has been reached. The implementation of Underscorejs Throttle is used as an example, and only some code examples are reserved. A key snippet is remaining = wait – (_now-previous).

// https://github.com/jashkenas/underscore/blob/master/modules/throttle.js#L23
export default function throttle(func, wait, options) {
  var timeout, context, args, result;
  var previous = 0;
  
  var throttled = function() {
    var _now = now();
    if(! previous && options.leading ===false) previous = _now;
    var remaining = wait - (_now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = _now;
      result = func.apply(context, args);
      if(! timeout) context = args =null; }};return throttled;
}
Copy the code

conclusion

The anti-shake function is executed after the event touches the specified time. If the event is triggered again within the specified time, the timer will be reset according to the last time. Throttling is the execution of a callback function at an interval after an event is triggered. These two concepts are encountered in front-end development, choose reasonable solutions to solve practical problems.

Anti – shake and throttling is not quite understood, according to the example of their own practice, have any questions in the comment area.

Reference

  • Jinlong. Making. IO / 2016/04/24 /…
  • Demo.nimius.net/debounce_th…
  • JavaScript Asynchronous Programming Guide – Explore event loops in the browser