Debounce and Throttle are two concepts we use in JavaScript to increase control over function execution, which is particularly useful in event handlers. Both techniques answer the same question, “How often is a function called over time?”

📚 Related links

Most of the content of the article comes from the following articles, assault delete!

  • Hackll.com/2015/11/19/…
  • Github.com/mqyqingfeng…
  • Drupalsun.com/david-corba…
  • Blog.coding.net/blog/the-di…
  • Github.com/jashkenas/u…
  • Github.com/jashkenas/u…

📚 Debounce

1. The concept

  • This is the concept of “going to bounce” of mechanical switch. After the spring switch is pressed, the contact point will be disconnected for many times continuously due to the action of the reed. If each contact is powered on, it is not good for the appliance, so it is necessary to control the power off during the period when the switch is pressed to stability

  • In front-end development, there are frequent event triggers

    • The mouse (mousemove…). The keyboard (keydown…). Events etc.
    • Real-time validation of forms (frequent validation requests)
  • Execute the callback after a delay of milliseconds when debounce is not called again, for example

    • inmousemoveEvent to ensure that the listener function is called only once for multiple triggers
    • In the form verification, do not add shake – proof, enter in turnuser, will be divided intou.us.use.userSend requests four times; And add anti – shake, set a good time, you can achieve complete inputuserBefore a verification request is issued

2. The train of thought

  • The debounce function accepts at least two arguments (popular libraries have three arguments)

    • The callback functionfn
    • Delay timedelay
  • The debounce function returns a closure that is called frequently

    • The debounce function is called only once, after which the closure it returns is called
    • Restrict callback functions inside closuresfnTo force the operation to be executed only once after the continuous operation is stopped
  • Closures are used so that variables that point to timers are not collected by gc

    • Implement in delay timedelayNone of the successive triggers infn, using a timer set inside a closuresetTimeOut
    • This closure is called frequently, and the last timer is cleared at each call
    • The variables held by the closure refer to the last timer that was set

3. The implementation

  • A simple implementation of the principle

    function debounce(fn, delay) {
      var timer;
      return function() {
        // Clear the timer set last time
        // The timer is cleared
        clearTimeout(timer);
        // Reset the timer
        timer = setTimeout(fn, delay);
      };
    }
    Copy the code
  • Code that is simply implemented can cause two problems

    • This refers to the problem. Debounce calls the callback fn in the timer, so when fn executes this points to a global object (window in the browser), this needs to be saved in an outer variable and explicitly bound using apply

      function debounce(fn, delay) {
        var timer;
        return function() {
          // Save this when called
          var context = this;
          clearTimeout(timer);
          timer = setTimeout(function() {
            // Correct the direction of this
            fn.apply(this);
          }, delay);
        };
      }
      Copy the code
    • The event object. JavaScript event handlers provide an event object that needs to be passed in when called in a closure

      function debounce(fn, delay) {
        var timer;
        return function() {
          // Save this when called
          var context = this;
          // Save the parameters
          var args = arguments;
          clearTimeout(timer);
          timer = setTimeout(function() {
            console.log(context);
            // Fix this and pass in the argument
            fn.apply(context, args);
          }, delay);
        };
      }
      Copy the code

4. Perfect (underscoreThe implementation of the)

  • Execute immediately. Add a third parameter, two cases

    • The callback function is executed firstfnAfter the trigger has stoppeddelayMilliseconds before it can be triggered again (To perform first)
    • Successive calls to debounce do not trigger the callback and stop the call afterdelayMilliseconds before executing the callback function (After the implementation)
    • clearTimeout(timer)Later,timerIt doesn’t becomenullInstead, it still points to the timer object
    function debounce(fn, delay, immediate) {
      var timer;
      return function() {
        var context = this;
        var args = arguments;
        // Stop the timer
        if (timer) clearTimeout(timer);
        // When the callback function executes
        if (immediate) {
          // Whether it has been executed
          // If yes, timer points to the timer object and callNow is false
          // The timer is null and the callNow is true
          varcallNow = ! timer;// Set the delay
          timer = setTimeout(function() {
            timer = null;
          }, delay);
          if (callNow) fn.apply(context, args);
        } else {
          // Stop the call before executing the callback
          timer = setTimeout(function() { fn.apply(context, args); }, delay); }}; }Copy the code
  • Return the value and cancel the debounce function

    • A callback may have a return value.
      • In the post-execution case, the return value can be ignored because the return value is always zero for the time before the callback function is executedundefined
      • In the first case, the return value is returned first
    • Can cancel the debounce function. Generally whenimmediatefortrueTrigger once and waitdelayTime, but to trigger again within this time period, you can cancel the previous debounce function
    function debounce(fn, delay, immediate) {
      var timer, result;
      var debounced = function() {
        var context = this;
        var args = arguments;
        // Stop the timer
        if (timer) clearTimeout(timer);
        // When the callback function executes
        if (immediate) {
          // Whether it has been executed
          // If yes, timer points to the timer object and callNow is false
          // The timer is null and the callNow is true
          varcallNow = ! timer;// Set the delay
          timer = setTimeout(function() {
            timer = null;
          }, delay);
          if (callNow) result = fn.apply(context, args);
        } else {
          // Stop the call before executing the callback
          timer = setTimeout(function() {
            fn.apply(context, args);
          }, delay);
        }
        // Returns the return value of the callback function
        return result;
      };
    
      // Cancel the operation
      debounced.cancel = function() {
        clearTimeout(timer);
        timer = null;
      };
    
      return debounced;
    }
    Copy the code
  • ES6 writing

    function debounce(fn, delay, immediate) {
      let timer, result;
      // The arrow function cannot be used here, otherwise this will still point to Windows objects
      // Use the rest argument to get the extra arguments of the function
      const debounced = function(. args) {
        if (timer) clearTimeout(timer);
        if (immediate) {
          constcallNow = ! timer; timer =setTimeout(() = > {
            timer = null;
          }, delay);
          if (callNow) result = fn.apply(this, args);
        } else {
          timer = setTimeout(() = > {
            fn.apply(this, args);
          }, delay);
        }
        return result;
      };
    
      debounced.cancel = () = > {
        clearTimeout(timer);
        timer = null;
      };
    
      return debounced;
    }
    Copy the code

📚 throttle

1. The concept

  • Fixed the rate at which a function executes

  • If events continue to fire, every once in a while, an event is executed

    • For example, listeningmousemoveEvent, regardless of the speed of mouse movement, the listener after throttling will execute at most once in the wait second and trigger execution at this constant speed
  • Window resize, Scroll event optimization, etc

2. The train of thought

  • There are two main implementations

    • Use time stamps
    • Set timer
  • The throttling function returns a closure when called

    • Closure is used to hold previous timestamp or timer variables (which cannot be collected by garbage collection because they are referenced by the returned function)
  • Timestamp mode

    • When the event is triggered, the current timestamp is retrieved and the previous timestamp is subtracted (initially set to 0)
    • If the result is greater than the set time period, the function is executed and then the timestamp is updated to the current timestamp
    • If the result is less than the set period, the function is not executed
  • Timer mode

    • Set a timer when an event is triggered
    • When the event is triggered again, if the timer is present, it is not executed until the timer is executed, and then the function is executed to clear the timer
    • Set the next timer
  • Combining the two methods, the merger can be executed immediately and executed once after the trigger is stopped

3. The implementation

  • Timestamp implementation

    function throttle(fn, wait) {
      var args;
      // The timestamp of the previous execution
      var previous = 0;
      return function() {
        // Convert the time to a timestamp
        var now = +new Date(a); args =arguments;
        // The interval is greater than the delay
        if (now - previous > wait) {
          fn.apply(this, args); previous = now; }}; }Copy the code
    • The listener event is triggered, and the callback function executes immediately (initial)previousIs 0, the difference must be greater than the interval unless the interval is set to be greater than the timestamp of the current time.)
    • After the trigger is stopped, no matter where the stop time is, it is no longer executed. For example, if the command is executed once every second and stops in 4.2 seconds, the command will not be executed again in 5 seconds
  • Timer implementation

    function throttle(fn, wait) {
      var timer, context, args;
      return function() {
        context = this;
        args = arguments;
        // If the timer exists, it is not executed
        if(! timer) { timer =setTimeout(function() {
            // Release timer variables after execution
            timer = null; fn.apply(context, args); }, wait); }}; }Copy the code
    • The callback function is not executed immediately; it is executed for the first time after the wait second, and again after the closure is stopped, if the stop time is between two executions
  • Combine timestamp and timer implementations

    function throttle(fn, wait) {
      var timer, context, args;
      var previous = 0;
      // delay execution of the function
      var later = function() {
        previous = +new Date(a);// Release timer variables after execution
        timer = null;
        fn.apply(context, args);
        if(! timeout) context = args =null;
      };
      var throttled = function() {
        var now = +new Date(a);// The time since the next fn execution
        // If the system time is manually changed, the current is smaller than previous
        // The remaining time may exceed the interval wait
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        / / no time remaining | | to modify system time lead to abnormal, fn, immediately executed a callback function
        // When first called, previous is 0, and the remaining time must be less than 0 unless wait is greater than the timestamp of the current time
        if (remaining <= 0 || remaining > wait) {
          // If there is a delay timer, cancel it
          if (timer) {
            clearTimeout(timer);
            timer = null;
          }
          previous = now;
          fn.apply(context, args);
          if(! timeout) context = args =null;
        } else if(! timer) {// Set execution delay
          timer = setTimeout(later, remaining); }};return throttled;
    }
    Copy the code
    • The throttling function in the process is implemented by the principle of time stamp, and at the same time implements the immediate execution
    • The timer is simply set to add a delay to the final exit
    • The timer is retimed each time it fires, but the callback function fn is not executed until it stops firing

4. Optimize and improve

  • Add a third parameter to allow users to choose their own mode

    • Ignore the call on the start boundary, passed in{ leading: false }
    • Ignore the call on the closing boundary, passed in{ trailing: false }
  • Added return value function

  • Add cancel function

    function throttle(func, wait, options) {
      var context, args, result;
      var timeout = null;
      // Last execution time point
      var previous = 0;
      if(! options) options = {};// Delay execution of the function
      var later = function() {
        // If the start boundary is set, the last execution time is always 0
        previous = options.leading === false ? 0 : new Date().getTime();
        timeout = null;
        // func may modify the timeout variable
        result = func.apply(context, args);
        // The timer variable reference is empty, indicating the last execution, and the variable referenced by the closure is cleared
        if(! timeout) context = args =null;
      };
      var throttled = function() {
        var now = new Date().getTime();
        // The last execution time is set to the current time if the start boundary is set for the first execution.
        if(! previous && options.leading ===false) previous = now;
        // Delay the execution interval
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        // The value of remaining is less than or equal to 0. It indicates that the interval of remaining exceeds one time window
        // Remaining Indicates that the system time of the client is adjusted if remaining is longer than wait of the time window
        if (remaining <= 0 || remaining > wait) {
          if (timeout) {
            clearTimeout(timeout);
            timeout = null;
          }
          previous = now;
          result = func.apply(context, args);
          if(! timeout) context = args =null;
        } else if(! timeout && options.trailing ! = =false) {
          timeout = setTimeout(later, remaining);
        }
        // Returns the value returned after the callback function is executed
        return result;
      };
      throttled.cancel = function() {
        clearTimeout(timeout);
        previous = 0;
        timeout = context = args = null;
      };
      return throttled;
    }
    Copy the code
    • There’s a question,leading: false å’Œ trailing: falseCannot be set at the same time
      • The first start boundary is not executed, but the first time it fires,previousTo 0, theremainingValues andwaitThe same. So,if (! previous && options.leading === false)True, changedpreviousThe value of andif (remaining <= 0 || remaining > wait)Is false
      • Triggering it in the future will causeif (! previous && options.leading === false)Is false, andif (remaining <= 0 || remaining > wait)Is true. It becomes the start boundary execution. So you andleading: falseThe conflict

📚 summary

  • At this point, one is fully implementedunderscoreThe Debounce and Throttle functions in
  • whilelodashThe debounce and Throttle functions are implemented more complex and packaged more thoroughly
  • Two tools for visualizing execution are recommended
    • Demo.nimius.net/debounce_th…
    • Caiogondim. Making. IO/js – debounce…
  • Implement your own to learn the ideas and use libraries like Lodash or underscore for your actual development.

contrast

  • Throttle and Debounce are two solutions to the request and response speed mismatch. The difference lies in the choice of strategy

  • The elevator timeout phenomenon explains the difference. Let’s say the elevator is set to 15 seconds, regardless of the capacity limit

    • throttleStrategy: Ensure that if the first person in the elevator comes in, 15 seconds after the punctual delivery, no waiting. If no one is in standby mode,
    • debounceStrategy: If someone enters the elevator, wait for 15 seconds. If someone enters the elevator again, re-time the elevator for 15 seconds. If no one enters the elevator again after 15 seconds, the elevator begins to transport