“This is the sixth day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Anti-shake and throttling are two solutions to problems such as the response not keeping up with the trigger frequency. When binding events to the DOM, there are events that we have no control over how often they fire. For example, the mouse movement event onMousemove, the scroll bar event onscroll, and the window size change event onresize will be triggered multiple times due to instantaneous operations. If the callback function of the event is complex, it will cause the response to fail to trigger, and the page will stagnate and fake death. For example, in the input box of Baidu, when checking the input in real time, if we bind onKeyUp event to send a request to the server for checking, the event trigger frequency will be very high in the user input process, resulting in a large number of requests, and the response speed will greatly lag behind the trigger.

Debounce and Throttling proposed two strategies to solve the problem of rapid sequential triggering and uncontrollable high-frequency triggering.

debounce

Debounce The policy is to set a period when the event is triggered to delay the execution of the action, if the period is triggered again, reset the period until the end of the period to execute the action. This was the basic idea behind debounce, which was extended in later stages to perform an action first and then set the cycle, within which events were fired, the action was not performed and the cycle was reset.

Continuous high-frequency operations trigger only one event, the delay is triggered after the high-frequency event is interpreted, and the leading edge is triggered at the beginning of the high-frequency event.

Delay Debounce, schematic diagram:

The cycle is updated each time an event is fired within the cycle, and the logic of the event is not executed until the end of the cycle

Leading edge Debounce, schematic diagram:

The logic of the event is executed at the beginning of the cycle, and the cycle is updated each time an event is fired within the cycle

Debounce is characterized by the fact that when events are triggered in rapid succession, the action is performed only once. Deferred debounces are performed at the end of the cycle and leading-edge debounces are performed at the beginning of the cycle. But when the trigger has a gap, and the gap is longer than the time interval we set, the action will be executed multiple times.

Version 1: new events are triggered within the cycle, so the old timer is cleared and the new timer is reset; This method requires the creation of timer at high frequency.

// Violent version: During the timer, there is a new operation, empty the old timer, reset the new timer
function debounce(fn, wait){
    let timer;
    return function(){
        // If the timer exists, it indicates that the current period is still in. You need to clear the old timer and create a new timer
        if(timer){
            clearTimeout(timer);        }
        // Create a timer to execute the function logic at the end of the cycle
        timer = setTimeout(() = >{
            fn.apply(this.arguments);
            clearTimeout(timer);
            timer = null; },wait); }}Copy the code

The simplest and most common implementation

Version 2: Reset the start time stamp of timer when a new event is triggered within the cycle. When the timer is executed, judge the start time stamp. Reset the delay timer if the start time stamp is delayed.

// Optimized version: When the timer is running, check whether the start time is delayed. If so, set the delay timer
function debounce(fn, wait){
    let timer, startTimeStamp = 0;
    let context, args;

    let run = (timerInterval) = > {
        timer = setTimeout(() = > {
            let now = Date.now();
            let interval = now - startTimeStamp
            if(interval < timerInterval){ // The start time of the timer has been reset, so the interval is less than timerInterval
                startTimeStamp = now;
                run(wait - interval);  // Reset the timer for the remaining time
            }else{
                fn.apply(context, args);
                clearTimeout(timer);
                timer = null;
            }

        }, timerInterval);
    }

    return function(){
        context = this;
        args = arguments;
        let now = Date.now();
        startTimeStamp = now;

        if(! timer){ run(wait);// A new cycle begins}}}Copy the code

You can also do this by recording the end time stamp

Version 3: Added whether to execute now option based on version 2:

function debounce(fn, wait, immediate=false){
    let timer, startTimeStamp=0;
    let context, args;

    let run = (timerInterval) = > {
        timer= setTimeout(() = > {
            let now = Date.now();
            let interval = now - startTimeStamp
            if(interval < timerInterval){ // The start time of the timer has been reset, so the interval is less than timerInterval
                startTimeStamp = now;
                run(wait - interval);  // Reset the timer for the remaining time
            }else{
                if(! immediate) { fn.apply(context, args); }clearTimeout(timer);
                timer = null;
            }

        }, timerInterval);
    }

    return function(){
        context = this;
        args = arguments;
        let now = Date.now();
        startTimeStamp = now;

        if(! timer){if(immediate) {
                fn.apply(context, args);
            }
            run(wait);    // A new cycle begins}}}Copy the code

throttling

Throttling: The throttling policy is to execute only one action within a fixed period. If a new event is triggered, the throttling policy is not executed. After the cycle ends, another event is triggered to start a new cycle. Throttling strategies are also divided into leading edge and delay. Similar to Debounce, delay is when the action is performed after the cycle is over, and leading edge is when the cycle starts after the action is performed.

Schematic diagram of delayed throttling:

Schematic diagram of leading edge Throttling:

Throttling features a smooth response in which the action is periodically performed when a continuous high-frequency event is triggered. In contrast to Debounce, Throttling does not update the cycle every time an event is triggered, and the new cycle is determined the first time it is triggered.

Version 1: The simplest implementation is to set the timer on the first execution and update the parameters if called again before the timer is executed.

function throttling (fn, wait, immediate) {
    let timer;
    let context, args;

    return function () {
        context = this;
        args = arguments;
        // If there is no timer, the current period is out
        if(! timer) {// Start a new cycle
            timer = setTimeout(() = > {
                if(! immediate) { fn.apply(context, args); }// The cycle ends
                clearTimeout(timer);
                timer = null; }, wait); }}}Copy the code

Version 2: Added leading-edge options: (For simpler cases, see underscope’s _.throttle for more complex cases)

function throttling (fn, wait, immediate = false) {
    let timer;
    let context, args;

    return function () {
        context = this;
        args = arguments;
        if(! timer) {if (immediate) {
                fn.apply(context, args);
            }
            // Start a new cycle
            timer = setTimeout(() = > {
                if(! immediate) { fn.apply(context, args); }// The cycle ends
                clearTimeout(timer);
                timer = null; }, wait); }}}Copy the code

Example: Update Echarts in Vue

We know that Echarts does not automatically update views based on data changes, and when we use Echarts in Vue, we expect Echarts to change views based on data changes, because it is more “Vue”.

So we can listen for changes in related data and re-call Echarts’ setOptions method to update the chart.

However, in actual development, we may modify options for several times, which will lead to setOptions being executed for many times. Since the cost of updating canvas whole graph is relatively high, we can use anti-shaking to control this process. When chart data is frequently modified, only the last modification will be updated to the page.

<template>
  <div id="main" ref="bar"></div>
</template>

<script>
import * as echarts from 'echarts'
function debounce(fn, wait, immediate=false){
  // ...
}
export default {
  name: 'Bar'.props: {
    options: {
      type: Object.default: () = > ({})
    }
  },
  mounted () {
    this.init()
    this.renderCharts()
  },
  methods: {init(){
      this.isInit = true;
      this.charts = echarts.init(this.$refs.bar)
    },
    // Render
    renderCharts:debounce(function (){
      if(this.isInit){
          this.charts.setOption(this.options)
      }
    },100)},watch: {// Listen for data changes and update the chart if it changes
    options: {
      deep: true.handler: this.renderCharts
    }
  }
}
</script>
Copy the code

Debounce and Throttling each have their own characteristics, and choose a strategy based on the requirements in different scenarios. If the event is triggered at high frequency but has a pause, select Debounce. Throttling is the only option when the event is triggered at a continuously high frequency, as debounce can cause the action to be performed only once and the interface to jump.