Update: Thank you for your support. Recently, a blog official website came out to facilitate your system reading. There will be more content and more optimization in the future

—— The following is the text ——

The introduction

In the previous section, we looked at the throttling function Throttle, and learned about its definition, implementation, and implementation in underscore. This section continues the discussion over debounce. The structure of the underscore function is the same, and the definitions, implementation principles, and two implementation codes are introduced separately. Finally, the underscore function is implemented.

Feel free to leave your thoughts or comments in the comments section. Below is a mind map of this article. See Github for hd mind maps and more articles.

Definition and Interpretation

The debounce function means that no matter how many callbacks are triggered in a given period of time, a function is executed only once. If we set a function to wait for 3 seconds, we will re-time the function for 3 seconds if we encounter a function call request within 3 seconds, until there is no function call request within 3 seconds, at which point we will execute the function, otherwise we will re-time the function and so on.

A small example: assume that was in a bus, the driver needs to wait for the last one to go and then close the door, every time a new one, the driver will put the timer reset and start the timer, to wait for a minute and then close the door, if subsequent 1 minutes without passengers get on the bus, the driver will think passengers came up, will close the door.

At this point, “passengers on board” is the callback task that is constantly flooding in as we frequently operate events; “1 minute” is the timer, which is the basis of the driver’s decision to “close the door”. If there is a new “passenger” on the bus, it will be cleared and reset the timer. “Close the door” is the last function that needs to be executed.

If you’re still confused, the image below makes it a lot clearer, and click on this page to see a visual comparison of throttling and anti-shock. Where Regular is the case where no processing is done, throttle is the result of throttling the function (as described in the previous section), and debounce is the result of buffeting the function.

Principle and Implementation

The implementation principle is to use timers. When the function is executed for the first time, a timer is set, and when it is called, the previous timer will be cleared and a new timer will be reset. If there is a timer that has not been cleared, the function will be triggered when the timer ends.

To achieve 1

/ / 1
// fn is the function that needs to be buffered
// Wait is an interval of time
function debounce(fn, wait = 50) {
    // Cache a timer ID through a closure
    let timer = null
    // Return the debounce result as a function
    // This return function is executed when the event callback is triggered
    return function(. args) {
      	// Clear the last timer if it has been set
        if (timer) clearTimeout(timer)
      
      	// Start a new timer, and execute the function fn when the timer ends
        timer = setTimeout(() = > {
            fn.apply(this, args)
        }, wait)
    }
}

// DEMO
// Execute debounce to return the new function
const betterFn = debounce(() = > console.log('FN anti-shake performed'), 1000)
// Stop sliding for 1 second and execute function () => console.log(' FN anti-shake executed ')
document.addEventListener('scroll', betterFn)
Copy the code

The 2

The above implementation is sufficient for most scenarios, but it is too difficult to execute fn the first time the callback event is triggered. Instead, we can rewrite debounce and execute it immediately the first time it is triggered.

/ / 2
// immediate Indicates whether the initial execution is immediate
function debounce(fn, wait = 50, immediate) {
    let timer = null
    return function(. args) {
        if (timer) clearTimeout(timer)
      
      	// ------ added part start ------
      	// Immediate If true, the command is executed after the first trigger
      	// If timer is empty, it is triggered for the first time
        if(immediate && ! timer) { fn.apply(this, args)
        }
      	// ------ added part end ------
      	
        timer = setTimeout(() = > {
            fn.apply(this, args)
        }, wait)
    }
}

// DEMO
// Execute debounce to return the new function
const betterFn = debounce(() = > console.log('FN anti-shake performed'), 1000.true)
// Trigger scroll to execute fn once for the first time, and then execute fn only after stopping sliding for 1 second
document.addEventListener('scroll', betterFn)
Copy the code

If the timer is null, the immediate &&! If timer returns true, the fn function is executed, fn.apply(this, args).

Enhanced throttle

Now consider the case that if the user’s operation is very frequent and the next operation does not wait for the set delay to end, the timer is frequently cleared and regenerated, so the function fn never executes, resulting in the user’s operation being delayed.

One idea is to combine “throttling” and “stabilization” into an enhanced throttling function. The key point is that “during the wait time, you can regenerate the timer, but when the wait time is up, you must give the user a response”. This combination of ideas can just solve the problem proposed above.

Before giving you the code for merging, review the throttle function, which was covered in detail in the previous section.

// fn is the function that needs to be executed
// Wait is an interval of time
const throttle = (fn, wait = 50) = > {
  // The last time fn was executed
  let previous = 0
  // return the throttle result as a function
  return function(. args) {
    // Get the current time and convert it to a timestamp in milliseconds
    let now = +new Date(a)// Compare the current time to the last time the function was executed
    // Set previous to the current time and execute fn if the wait time is greater than that
    if (now - previous > wait) {
      previous = now
      fn.apply(this, args)
    }
  }
}
Copy the code

With throttle and debounce, the throttle function is throttle. The new logic is to set up a new timer when the time difference between the current trigger time and the last trigger time is less than the interval, which is equivalent to putting debounce code in the part that is less than the interval.

// fn is the function that needs throttling
// Wait is an interval of time
function throttle(fn, wait) {
  
  // Previous is the time when fn was last executed
  // timer is a timer
  let previous = 0, timer = null
  
  // return the throttle result as a function
  return function (. args) {
    
    // Get the current time and convert it to a timestamp in milliseconds
    let now = +new Date(a)// ------ added part start ------
    // Check whether the difference between the last trigger time and the current trigger time is smaller than the time interval
    if (now - previous < wait) {
    	 // If less than, set a new timer for the trigger operation
       // The fn function is executed when the timer time expires
       if (timer) clearTimeout(timer)
       timer = setTimeout(() = > {
          previous = now
        	fn.apply(this, args)
        }, wait)
    // ------ added part end ------
      
    } else {
       // This is the first execution
       // Or if the interval exceeds the specified interval, the function fn is executed
       previous = now
       fn.apply(this, args)
    }
  }
}

// DEMO
// Execute throttle to return the new function
const betterFn = throttle(() = > console.log('FN throttle executed'), 1000)
// Trigger scroll to execute fn once for the first time, execute function fn once every 1 second, stop sliding for 1 second and then execute function fn again
document.addEventListener('scroll', betterFn)
Copy the code

If you look at the entire code, you’ll see that this idea is very similar to the one you implement for Throttle in underscore, described in the previous article.

Underscore source code

Underscore is a simple way to implement debounce functions and to get a good idea of directly using the code and annotations. The source code parsing relies on the 1.9.1 version of underscore.

// All three parameters are explained above
_.debounce = function(func, wait, immediate) {
  // timeout Indicates a timer
  // result indicates the return value of the func execution
  var timeout, result;

  // After the timer finishes
  // 1. Clear the timer so that it does not affect the triggering of the next consecutive event
  // execute func
  var later = function(context, args) {
    timeout = null;
    // If (args) judgments are triggered immediately for filtering
    // The connection is _. Delay and restArguments
    if (args) result = func.apply(context, args);
  };

  // Return the debounce result as a function
  var debounced = restArguments(function(args) {
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      // Set timeout for the first time,
      // If timeout is null, you can determine if it is the first time
      varcallNow = ! timeout; timeout =setTimeout(later, wait);
      if (callNow) result = func.apply(this, args);
    } else {
    	// Set the timer
      timeout = _.delay(later, wait, this, args);
    }

    return result;
  });

  // Add manual cancel
  debounced.cancel = function() {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
};

// Execute the function func based on the given millisecond wait delay
_.delay = restArguments(function(func, wait, args) {
  return setTimeout(function() {
    return func.apply(null, args);
  }, wait);
});
Copy the code

Over the basic version implementation above, underscore has the following features.

  • Func returns result after the function is executed
  • 2. Clear the timeout after the timer finishes, so that it does not affect the triggering of the next continuous event
  • 3. Added the manual cancellation function cancel
  • 4. If immediate is true, the command is executed only when the callback is triggered for the first time

summary

  • Function throttling and anti-shaking are applications of “closures” and “higher-order functions”

  • Function throttling refers to the execution of a function once in an interval of three seconds, for example, during which subsequent function calls are ignored

    • Throttling can be understood as tightening the faucet to drain water when raising goldfish, one drop in three seconds
      • The “water in the pipeline” is the callback task that comes in because we frequently manipulate events, and it needs to accept the “faucet” arrangement
      • The “faucet” is the throttle valve that controls the flow of water and filters out ineffective callback tasks
      • A drip is a function executed every once in a while
      • “3 seconds” is the interval time, it is the “faucet” decision “drip” basis
    • Application: Listen for scrolling events after adding the throttling function, execute it at regular intervals
    • Implementation scheme 1: Use the timestamp to determine whether the execution time has reached, record the timestamp of the last execution, and execute the callback after each trigger to determine whether the interval between the current time and the last execution time has reached the time difference (Xms). If yes, execute and update the timestamp of the last execution, and so on
    • Implementation scheme 2: Use a timer. For example, when the Scroll event is triggered, print onehello worldAfter that, each scroll event is triggered to trigger the callback. If a timer already exists, the callback will not execute the method until the timer is triggered and the handler is cleared. Then, the timer is reset
  • Debounce means that no matter how many callbacks are triggered in a given period of time, a function is executed only once

    • Anti-shaking can be understood as the driver waiting for the last person to enter before closing the door, each time a new person enters, the driver will clear the timer and restart the clock

      • The “passenger on board” is the callback task that comes in constantly as we frequently manipulate events
      • The “1 minute” is the timer, which is the basis of the driver’s decision to “close the door”. If a new “passenger” gets on the bus, the clock will be cleared and reset
      • “Close the door” is the last function that needs to be executed
    • Application: Input Input callback event After adding an anti-shake function, it will only trigger once after stopping input

    • Implementation scheme: the use of timer, the function for the first time when the execution of a timer, after the call found that the timer has been set before the empty timer, and reset a new timer, if there is no empty timer, when the timer ends to trigger the function execution

reference

underscore.js

Front-end performance optimization principles and practices

Article shuttle

  • 【 Advanced stage 6-3 】 Shallow throttling function throttle
  • Further currification of applications of higher-order functions
  • A Brief analysis of JavaScript higher-order functions
  • [Advanced 5-3] Further explore the Function & Object egg problem
  • Step 5-2: Diagram prototype chains and their inheritance advantages and disadvantages
  • Reconceptualize constructors, stereotypes, and stereotype chains