Function tremble and function throttling

concept

Debounce: The event is executed at time t after it is triggered. If the event is triggered again within this time interval t, the time is recalculated.

Function throttling: No matter how many events are fired within the interval t, only one event is executed.

Function image stabilization

The simplest function for shaking

Step by step, learn more about function stabilization with code that runs directly.

The sample

<! -- index. HTML -- function shaker example -->
<! DOCTYPEhtml>
<html lang="zh">
<head>
    <meta charset="utf-8">
    <meta name="author" content="xiangchengyu">
</head>
<body>
    <button id="js_debounce">Image stabilization</button>

    <script type="application/javascript">! (function (global) {
        let element = {
            debounce: document.getElementById('js_debounce')}; element.debounce.addEventListener('click', debounce(onClick, 2000));

        function onClick(event) {
            console.log(event);
        }

        /** function anti-shake */
        function debounce (fn, delay)
        {
            console.log("I'm being executed!"); // When do I execute?
            let timer = null;
            return function() {
                let params = arguments;
                clearTimeout(timer);
                timer = setTimeout(function() {
                    fn && fn.apply(this, params);
                }, delay);
            };
        }
    })(this);
    </script>
</body>
</html>
Copy the code

The code analysis

Before doing code analysis, let me ask the following question:

  1. When is code ① executed?

Wrong answer: Execute immediately after each debounce button is clicked.

If you fall into the trap of thinking there is something wrong with the above anti-shock code, you might wonder: Wouldn’t debounce be called every time you clicked? Let timer = null and then clearTimeout(timer).

In fact:

/* 'debounce(onClick, 2000)' is a complete function expression, i.e. a function call. Here 'debounce(onClick, 2000)' executes immediately when the 'click' event is bound and returns a function, which executes the anonymous function returned by 'debounce(fn, 2000)' whenever we trigger the click event. * /
element.debounce.addEventListener('click', debounce(onClick, 2000));

// Provide code 1 for comparison
element.debounce.addEventListener('click', onClick);

// Provide code 2 for comparison
element.debounce.addEventListener('click', onClick());
Copy the code

So code ① executes immediately after the click event is bound, not every time it is clicked.

The code analysis

// debounce: the __ event is executed at time t after it is triggered,
// If the event is triggered again within the interval t, the time is recalculated.

function debounce(fn, delay) { // ==> function scope ①
    let timer = null; // the variable that belongs to function scope ①
    return function() { // ==> function scope ②
        let params = arguments; // The parameter of the anonymous function ② (to obtain the parameter of the protected function)
        clearTimeout(timer); // Clear timer in scope ①
        timer = setTimeout(function () { // ==> function scope ③, change function scope ② timer value
            fn && fn.apply(this, params); // params is obtained from function scope ②
            // Use apply to avoid using '... 'Extension extension operator, there is a certain compatibility oh!
            // fn && fn.call(this, ... arguments);}, delay); }}// The function that needs to be buffered
function fn(str) {
    console.log("= = = = >" + str);
}

// Get the anonymous function ②
let debounceFn = debounce(fn, 3000);

// Execute the anti-shake function
debounceFn('Hello World! ');
Copy the code

Dblclick is not twice click

As we all know, a double click is a combination of two clicks. But most of the time, we don’t want to trigger a click event when we double click.

<! DOCTYPEhtml>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <button id="js_button">Don't talk. Kiss me</button>

    <script>
        let btn = document.getElementById('js_button');
        
        // ① Quickly click the button several times (more than three times and fast) and observe the output result.
        btn.addEventListener('click', handleClick);
        btn.addEventListener('dblclick', handleDoubleClick);

        function handleClick(event) {
            console.log("Please love me again!");
        }

        function handleDoubleClick(event) {
            console.log("Liking is pursuit, love is restraint!");
        }
    </script>
</body>
</html>
Copy the code

Run the code above and you’ll see that double-clicking the button also triggers the click event. Now the demand is: click trigger please love me again! , double click only trigger like is pursuit, love is restraint! .

Double click is strict twice click! (double click is strict twice click! , the click event is triggered each time.

Let’s add some code to meet our requirements:

<! Double click is double click -->
<! DOCTYPEhtml>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <button id="js_button">Stop talking and click on me</button>

    <script>
        let btn = document.getElementById('js_button');

        let debounceFn = debounce(dispatcher, 600);

        btn.addEventListener('click', debounceFn);
        btn.addEventListener('dblclick', debounceFn);

        /* Event dispatcher */
        function dispatcher(event) {
            switch (event.type) {
                case 'click':
                    handleClick(event);
                    break;
                case 'dblclick':
                    handleDoubleClick(event);
                    break;
                default:
                    console.warn('unknown event type');
                    break; }}/* Click the event handler */
        function handleClick(event) {
            console.log("Please love me again!");
        }

        /* Double-click the event handler */
        function handleDoubleClick(event) {
            console.log("Liking is pursuit, love is restraint!");
        }

        /* Anti-shake function */
        function debounce(fn, delay) {
            let timer = null;
            return function() {
                let params = arguments;
                clearTimeout(timer);
                timer = setTimeout(() = > {
                    fn && fn.apply(this, params);
                }, delay);
            };
        }
    </script>
</body>
</html>
Copy the code

Test run, perfect implementation of requirements.

The anti-shake function can be executed immediately

“Why is it that the first time you click it feels like you’re stuck? Can it be done immediately?” Customers say. I have to say, sometimes the demands of customers are tricky but very reasonable. The purpose of this function is to prevent the user from triggering events frequently. If the code execution is slow and the user clicks frantically, won’t it backfire?

/* Function anti-shake - immediately executes the anti-shake function */
function debounce(fn, delay, immediate) {
    let timer = null;
    let _immediate = immediate;
    return function() {
        let params = arguments;
        if (_immediate && immediate) {
            fn && fn.apply(this, params);
            _immediate = false;
        }
        clearTimeout(timer);
        timer = setTimeout(function() { (_immediate || ! immediate) && fn && fn.apply(this, params);
            _immediate = true; }, delay); }}Copy the code

Cancelable anti-shake function

Everyone should have played QQ! QQ has a function that allows a cursor to hover over a friend’s profile picture, and a message panel pops up around the main panel. If the cursor moves quickly over multiple friends’ faces, the message panel will not be displayed. The message panel will only be displayed if the cursor hovers over the head for a while or if the cursor is hovering over the message panel. (Try it out and think about how you would do it.)

Suppose I wanted to implement something similar now, and my thinking would be something like this:

  1. Write an anti-shaking function to avoid frequent events triggered by the display information panel after the cursor passes through the head quickly;
  2. When the cursor mouseEnter reaches the head, the anti-shaking function is performed to display the information panel after the cursor is suspended for a while.
  3. If the cursor suspension time is short, thenCancel the executionDisplay information panelThe method of, that is, improvement steps1Anti – shake function of.
  4. Assuming that the information panel is displayed, the cursor moves from the avatar to the information panel (the cursor moves out of the avatar and not into the panel)Hide information PanelMethods. If the cursor moves from the avatar to the info panel for a short time (the info panel is not hidden)Cancel the executionHide information PanelThe method of.

The cancelable anti – shake function is easy, just add a method to clear the timer. The code snippet is as follows:

/* Function stabilization - cancelable stabilization function */
function debounce(fn, delay) {
    let timer = null;
    
    debounce.cancel = function() {
        clearTimeout(timer);
    }

    return function() {
        let params = arguments;
        clearTimeout(timer);
        timer = setTimeout(function() {
            fn && fn.apply(this, params); }, delay); }}Copy the code

QQ’s logic isn’t that complicated

The following code implements the above requirements.

<! DOCTYPEhtml>
<html>
<head>
    <meta charset="utf-8">
    <style>
        .avatar {
            width: 60px;
            height: 60px;
            background-color: gray;
            text-align: center;
            line-height: 60px;
        }
        .circle {
            border-radius: 50%;
        }

        .panel {
            display: none;
            width: 100px;
            height: 100px;
            margin-top: 20px;
            background-color: red;
        }
    </style>
</head>
<body>
    <div id="js_avatar" class="avatar circle">Suspended I</div>

    <div id="js_panel" class="panel">panel</div>

    <script>
        let isShowPanel = false;

        let avatarElem = document.getElementById('js_avatar');
        let panelElem = document.getElementById('js_panel');

        let debounceFn = debounce(dispatcher, 1000.false);

        avatarElem.addEventListener('mouseenter', debounceFn);
        avatarElem.addEventListener('mouseleave', debounceFn);
        panelElem.addEventListener('mouseenter', debounceFn);
        panelElem.addEventListener('mouseleave', debounceFn);

        /* Event dispatcher */
        function dispatcher(event) {
            switch (event.type) {
                case 'mouseenter':
                    handleMouseEnter(event);
                    break;
                case 'mouseleave':
                    handleMouseLeave(event);
                    break;
                default:
                    console.warn('unknown event type');
                    break; }}function showPanel() {
            console.log("ShowPanel my logic was executed.");
            panelElem.style.display = 'block';
            isShowPanel = true;
        }

        function hidePanel() {
            console.log("HidePanel my logic was executed");
            panelElem.style.display = 'none';
            isShowPanel = false;
        }
        
        /* Click the event handler */
        function handleMouseEnter(event) {
            // showPanel(); // Do not cancel the anti-shake, every time will execute oh!
            // Unhide
            isShowPanel ? debounce.cancel() : showPanel();
        }

        /* Double-click the event handler */
        function handleMouseLeave(event) {
            // hidePanel(); // Do not cancel the anti-shake, every time will execute oh!
            // Cancel the display! isShowPanel ? debounce.cancel() : hidePanel(); }/* Anti-shake function */
        function debounce(fn, delay, immediate) {
            let timer = null;
            let _immediate = immediate;
            
            debounce.cancel = function() {
                clearTimeout(timer);
            }

            return function() {
                let params = arguments;
                if (_immediate && immediate) {
                    fn && fn.apply(this, params);
                    _immediate = false;
                }
                clearTimeout(timer);
                timer = setTimeout(function() { (_immediate || ! immediate) && fn && fn.apply(this, params);
                    _immediate = true; }, delay); }}</script>
</body>
</html>
Copy the code