Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

preface

Multi-gif + step details principle detailed explanation, take you seconds to understand the front-end interview handwritten “throttling anti shake”.

The purpose of both stabilization and throttling is to prevent multiple calls to the function. The difference is that if a user fires the function all the time and the interval is shorter than the set time, the function will only be called once in the case of stabilization, while the function will be called once in the case of throttling.

Image stabilization

Debounce: A function can only be executed once within n seconds after an event is triggered. If an event is triggered again within n seconds, the function execution time is recalculated.

Anti – shake function is divided into non – immediate execution version and immediate execution version.

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.

Not an immediate release

// Anti-jitter function
function debounce(func,delay) {
    let timer;
    return function() {
    	let context = this;
        if(timer) clearTimeout(timer) // Clear the previous setTimeout each time
        timer = setTimeout(() = > {
            func.apply(context,arguments)
        },delay)
    }
}
Copy the code

Analysis of the main points of the above implementation of anti-shake function:

0. In understanding the image stabilization function before, we need to know the two basic concepts, the two basic concept is the premise and necessary understanding of image stabilization function, this part of the content is written in the paper about the immediate execution version of the image stabilization function there, because writing a test code to see the effect, the test code using examples are executed immediately version of the image stabilization function. Click me to jump to that part of the content

1. Under continuous click events, multiple independent executing functions will be returned. If we want these independent executing functions to be connected, we need to use the scope chain, that is, the closure. We need to do is just put the timer the definition of the variables on the return to peripheral function, so that we in the definition to monitor events when the timer variables at the same time, because of the scope chain, all independent executive function can access to this timer variables, and now the timer variable is created only once, Is unique, we just keep delaying the timer assignment. Each clearing delay is the clearing delay of the previous definition, which is equivalent to multiple functions sharing the same external variable

2. In the timer (setTimeout), this points to the window. Normally, we bind an event to the button, and this should point to the button label. So we can save this before setTimeout let context = this; , then we use apply in setTimeout to bind this to the method to be executed, so that this pointing to the correct, this pointing to the correct to complete the normal function of the anti-shake function, otherwise binding things changed, how can we achieve anti-shake?

3. Use the arrow function, there is no need to before the setTimeout method “let the args = the arguments”, because the arrow arguemtns is outer function in the function of the arguments

4. With respect to the timeoutID returned by timer setTimeout(), it should be clarified that this timeoutID is generated when setTimeout() is executed, not when timer setTimeout() expires. See the following code for details:

<body>
    <script>
        let timer = setTimeout(() = > {
            console.log("Time's up");
        }, 3000)
        console.log('timer :>> ', timer);
        let timer2 = setTimeout(() = > {
            console.log("Time's up");
        }, 3000)
        console.log('timer2 :>> ', timer2);
    </script>
</body>
Copy the code

When the code runs, output two timeoutID, namely two timer in the above code, and output the code to be executed after three seconds delay. The execution situation is as follows (note that the following GIF is usually about three seconds, do not walk away at a glance) :

Example application of anti-shake function:

Let’s take a look at what happens when a function is executed frequently while the event continues to fire.

<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>

<body>
    <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');

        function count() {
            content.innerHTML = num++;
        };
        content.onmousemove = count;
    </script>
</body>

</html>
Copy the code

In the code above, the div element is bound to the Mousemove event, which is continuously triggered when the mouse moves over the div (gray) area, causing the function to execute frequently. Results the following

For the example above, we can use the anti-shake function as follows:

content.onmousemove = debounce(count,1000);
Copy the code

Example code:

<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>

<body>
    <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');

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

        // Anti-jitter function
        function debounce(func, delay) {
            let timer;
            return function () {
                let context = this;
                if (timer) clearTimeout(timer) // Clear the previous setTimeout each time
                timer = setTimeout(() = > {
                    func.apply(context, arguments)
                }, delay)
            }
        }

        content.onmousemove = debounce(count, 500);
    </script>
</body>

</html>
Copy the code

The effect is as follows:

An explanation of the problem of getting Event E in an execution function

If we do not use the arrow function in the timer and we do not define arguments in advance, we will not get the Event parameter Event E.

  // Anti-jitter function
  function debounce(func, delay) {
      let timer;
      return function () {
          let context = this;
          // let args = arguments;
          if (timer) clearTimeout(timer) // Clear the previous setTimeout each time
          // timer = setTimeout(() => {
          // func.apply(context, arguments)
          // }, delay)
          timer = setTimeout(function () {
              func.apply(context, arguments)
          }, delay)
      }
  }

  content.onmousemove = debounce(count, 500);
Copy the code

Use the arrow function, or define arguments in advance, to get the correct e, which is arguments

   // Anti-jitter function
   function debounce(func, delay) {
       let timer;
       return function () {
           let context = this;
           // let args = arguments;
           if (timer) clearTimeout(timer) // Clear the previous setTimeout each time
           timer = setTimeout(() = > {
               func.apply(context, arguments)
           }, delay)
           // timer = setTimeout(function () {
           // func.apply(context, arguments)
           // }, delay)
       }
   }

   content.onmousemove = debounce(count, 500);
Copy the code

Immediate execution version

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.

// Anti-jitter function - run now version
function debounce(func, delay) {
    let timer;
    return function () {
        let context = this;

        if (timer) clearTimeout(timer); // Clear the previous setTimeout each time

        letcallNow = ! timer; timer =setTimeout(() = > {
            timer = null;
        }, delay)

        if (callNow) func.apply(context, arguments); }}Copy the code

Prerequisites for understanding the anti-shake function:

Take a look at the sample code:

<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>

<body>
    <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');

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

        // Anti-jitter function
        function debounce(func, delay) {
            let timer;
            return function () {
                let context = this;

                if (timer) clearTimeout(timer); // Clear the previous setTimeout each time

                letcallNow = ! timer; timer =setTimeout(() = > {
                    timer = null;
                }, delay)

                if (callNow) func.apply(context, arguments)
            }
        }

        content.onmousemove = debounce(count, 500);
    </script>
</body>

</html>
Copy the code

Two very basic concepts we need to know are:

1. Js event binding, function name with parentheses and no parentheses difference

Js event binding, the function is parenthesized to indicate immediate execution. The event-bound function will execute the function immediately when the page is loaded in parenthesized function (i.e. execute the function once without triggering the event when the page is loaded here).

If you don’t put parentheses on it, you get the body of the function, the function itself, it doesn’t execute the function.

<body>
    <div>Whether a function call should be parenthesized</div>
    <button>Click on the color</button>
    <script type="text/javascript">
      var div = document.getElementsByTagName('div') [0];
      var btn = document.getElementsByTagName('button') [0];

        function reset(){
            div.style.color='green'
        }
        btn.onclick = reset Btn.onclick = function reset(){btn.onclick = function reset(){... }, execute the event after clicking, resulting in the function body
        btn.onclick = reset()  //2. This can be interpreted as the immediate execution of a function by enclosing parentheses. The page is loaded on this line and the function is executed immediately
    </script>
</body>

Copy the code

2. Back to the anti-shock function, in this line of code Content.onMousemove = debounce(count, 500); Onmousemove binds the anti-shock function debounce(count, 500) that has been passed in to execute count();

ifonmousemove The binding is just a normal functioncount()Each mouse movement event listener executes the function in its entiretycount()But what we bind now is a higher-order function, the closure, which is the anti-shake functiondebounceReturns another function that is executed once during compilation and rendering, just like the normal function in parenthesesdebounceThe code before the return function, i.elet timer;Each subsequent mouse movement event listener will only execute another function of its return, that is, the complete anti-shake functiondebounceIt only executes once, when the page loads, leaving the rest of the listening to its return function. This is the core conceptual understanding of the anti – shake function.

The body of the higher-order debounce function itself is just the code before the return, that is, the let timer; This line declares variable code, so the let timer is executed once and only when the page loads; And returns another function to the listener, Content.onMousemove.

Let’s add some test code and write a test program to prove our previous results, and also to show the effect of the stabilization function using the closure scope chain:

<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>

<body>
    <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');

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

        let count2 = 0;

        function debounce(func, delay) {
            count2++;
            let timer;
            console.log("Return before the first" + count2 + "Time" + "timer:" + timer);
            return function () {
                let context = this;

                console.log("Return, setTimeout" + count2 + "Time" + "timer:" + timer);
                if (timer) clearTimeout(timer); // Clear the previous setTimeout each time

                letcallNow = ! timer; timer =setTimeout(() = > {
                    timer = null;
                }, delay)

                console.log("Return, setTimeout" + count2 + "Time" + "timer:" + timer);
                if (callNow) func.apply(context, arguments)

                count2++;
            }
        }


        content.onmousemove = debounce(count, 500);
    </script>
</body>

</html>
Copy the code

Effect:

After the page is loaded, the code before the debounce function returns is executed once and only once, namely the let timer; , the effect is as follows:

When the listener is triggered, it can be found that only the function after the return will be executed. Note that the console prints only the number of timers before the return, and the rest are after the return. In addition, we can also see that the closure’s scope chain function is used to achieve the anti-shake function. Notice that the timer variable before each execution of setTimeout is the timer ID generated by the last click. In this way, the function of clearing the previous setTimeout is realized each time.

Double sword combined round edition

In the development process, we need to decide which version of the anti-shake function we need to use according to different scenarios, generally speaking, the above anti-shake function can meet most of the requirements of the scene. However, we can also combine the non-immediate version and immediate version of the anti-shake function, to achieve the final version of the double sword anti-shake function.

/ * * *@desc Function stabilization *@param Func function *@param Number of milliseconds to delay wait execution *@param Immediate true The table is executed immediately. False The table is not executed immediately */
function debounce(func, delay, immediate) {
    // Double sword combined round version
    let timer;
    return function () {
        let context = this;
        
        if (timer) clearTimeout(timer);
        if (immediate) {
            letcallNow = ! timer; timer =setTimeout(() = > {
                timer = null;
            }, delay)
            if (callNow) func.apply(context, arguments);
        } else {
            timer = setTimeout(() = > {
                func.apply(context, arguments);
            }, delay)
        }
    }
}
Copy the code

Application scenarios of anti-shake

  • Restricted mouse combo trigger
  • The resize/scroll statistics event is triggered every time
  • Validation of text input (send AJAX request for validation after continuous text input, once validation is fine)

The throttle

Throttling: Throttling means firing events continuously but executing functions only once in n seconds. Throttling dilutes the execution frequency of the function.

For throttling, there are generally two ways to achieve, respectively, the timestamp version and the timer version.

The time stamp version

function throttle(func, wait) {
    // Time stamp version
    let previous = 0;
    return function () {
        let now = new Date(a);if (now - previous > wait) {
            previous = now;
            func.apply(this.arguments)}}}Copy the code

The timestamp version doesn’t use timers, so you don’t need to declare a this variable.

The timer version

function throttle(func, wait) {
    // Timer version
    let timer;
    return function () {
        let context = this;
        if(! timer) { timer =setTimeout(() = > {
                timer = null;
                func.apply(context, arguments)
            }, wait)
        }
    }
}
Copy the code

Application scenarios of throttling

  • Shooter mouseDown/KeyDown events (only one shot per unit of time)
  • Search for Associations (KeyUp)
  • Listen for scroll events to determine whether more automatically loads at the bottom of the page. After adding debounce to Scroll, users will only know whether they have reached the bottom of the page after they stop scrolling. If you’re throttle, you’re going to do it every time you scroll

reference

  • Functions for anti-shake and throttling – Jianshu.com
  • What you need to Know about your job search
  • JavaScript stabilization _bilibili bilibili