Yesterday analyzed the source of the anti – shake function, today to look at throttling.

The so-called throttling, in fact, is like controlling the tap water not to flow too much at once, so control it slowly according to a certain speed to flow down. That is, in the case of continuous firing, the control function continues to execute for a certain amount of time.

Let’s see how it works.

// Wrap the function doSomething with a throttling function
// _.throttle(func, [wait=0], [options={}])
let doSome = throttle(doSomeThing, 1000, {
    leading: true.trailing: true
})
// Triggered when the mouse moves over the Container
container.onmousemove = doSome;
Copy the code

As you can see, in addition to the function that needs to be executed, the delay time, there is a third argument, which is an object.

The opitons parameter defines some options:

  • leading, the function is called at the start of each wait delay. The default value isfalse
  • trailing, the function is called at the end of each wait delay. The default istrue

Depending on the combination of leading and trailing, different call effects can be implemented:

  • The first:leading-true, trailing-false: Is invoked only when the delay starts and not when the delay ends
  • The second:Leading - false, trailing - true: The default, that is, the function is called after the delay ends
  • The third:Leading - true, trailing - true: called at the start of a delay and after the delay ends

Note: there is no trailing false or leading-false.

We can start by looking at how the first case works. To call immediately at the beginning and not at the end, we can use a timestamp to do that. At first, the difference between the current time and the previous time should be greater than the delay time. If the difference does not exceed the delay time, the last time will not be executed.

In the beginning, we can directly obtain the current time, set the previous time to 0, and then the difference between the two values must be greater than the delay time, that is, it can be executed immediately.

// It starts immediately, but if it stops firing before the trigger time is reached, the function is not executed.
// Equivalent to:
// let doSome = _.throttle(doSomeThing, 1000, {
// leading: true,
// trailing: false
// });
// The trigger function is not called at the end
function throttle(func, wait){
    let context, args;

    // The previous timestamp
    let old = 0;

    return function(){
        // Change the inner this pointer
        context = this;
        // Pass the argument to the function that actually executes in order to get the event event object
        args = arguments;

        // Get the current timestamp
        let now = new Date().valueOf();

        If the current time minus the previous trigger event equals the delay time, the function is executed
        if(now - old > wait){
            // Execute immediately
            func.apply(context, args);
            // Assign the current time to the old timeold = now; }}Copy the code

Now let’s look at the second case.

// Equivalent to:
// let doSome = _.throttle(doSomeThing, 1000, {
// leading: false,
// trailing: true
// });
// The first time will not trigger, the last time will trigger
function throttle(func, wait){
    let context, args, timeout;

    return function(){
        // Change the inner this pointer
        context = this;
        // Pass the argument to the function that actually executes in order to get the event event object
        args = arguments;

        // If no timer has been set, set timer delay
        if(! timeout){ timeout =setTimeout(() = > {
                // When the delay time is reached, empty the delay timer and execute the function.
                // After the timer is empty, if it continues to trigger, the timer will continue to be set and the process will be repeated
                timeout = null; func.apply(context, args); }, wait); }}}// We can find that the initial trigger is not executed immediately, because the timer is set to delay execution.
// If the timer is empty, the timer will be reset before the delay time is reached, and the timer will be executed again after the delay time is reached.
Copy the code

Finally, let’s look at how the source code is implemented to combine the two together!

_.throttle = function(func, wait, options) {

  var timeout, context, args, result;
  
  // The previous timestamp
  var previous = 0;
  
  // If the third argument is not passed, it is set to an empty object to prevent access problems
  if(! options) options = {};// Empty the timer and execute it immediately. timeout && options.trailing ! If == false, the timer is empty
  var later = function() {
  
  // If leading is false, set it to 0; otherwise, select current time, assign it to it, update previous
    previous = options.leading === false ? 0 : _.now();
    
    // Empty the timer
    timeout = null;
    // Execute the function immediately
    result = func.apply(context, args);
    // Prevent memory leaks
    if(! timeout) context = args =null;
  };

  var throttled = function() {
  // Get the current time
    var now = _.now();
    
    // If leading is set to false and previous is empty, set the previous time to the current time. // If leading is false and previous is empty, set the previous time to the current time.
    // Remaining is equal to wait and does not enter the first judgment
    if(! previous && options.leading ===false) previous = now;
    
    // Time remaining until next execution: delay time minus (the difference between present time and past time)
    var remaining = wait - (now - previous);
    
    // Change the inner this pointer
    context = this;
    // Pass the argument to the function that actually executes in order to get the event event object
    args = arguments;
    
    // The remaining time is less than or equal to 0 (in the first case, the execution is triggered by the timestamp) or greater than the delay time (past time is later than the present time)
    if (remaining <= 0 || remaining > wait) {
    // If there is already a timer, it can be cleared directly to prevent the timestamp mode and timer mode conflict
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      // Assign the present time to the previous time
      previous = now;
      // Execute immediately and return the result to result
      result = func.apply(context, args);
      // Prevent memory leaks
      if(! timeout) context = args =null;
      
    } else if(! timeout && options.trailing ! = =false) {
    // If the timer is empty, reset the timer
      timeout = setTimeout(later, remaining);
    }
    
    // Returns the result of the original function execution
    return result;
  };
  
  // If the wait is delayed for a long time, cancel can be called midway.
  throttled.cancel = function() {
    clearTimeout(timeout);	// Empty the timer
    previous = 0;	// Reset the previous time to 0
    timeout = context = args = null;	// Prevent memory leaks
  };	
  
  // Return the wrapped function object
  return throttled;
};
Copy the code