Anyone who has used the LoDash library should be familiar with _. Debounce and _. Throttle, the functions debounce and throttle.

  • What are the functions of anti-shake and throttling?
  • Why is the Throttle function in JavaScript Advanced Programming written the same as anti-shock?
  • What is the relationship between Debounce and Throttle in the Lodash library, and why does Throttle return the execution of Debounce?

concept

To learn about debounce, you can start with bounce:

Contact bounce is a common problem with mechanical switches and relays. The contacts between switches and relays are usually made of elastic metal. When the contacts are struck together, the impact and spring forces of the contacts together cause the bouncing part to occur one or more times before the contacts stabilize. The way to eliminate the leaps to the contacts is the so-called “debouncing” circuits.

The term “debounce”, derived from this, is used in the software development industry to describe a ratio restriction or frequency adjustment that eliminates a switch bounce implementation.

About throttle:

The throttle valve is a fluid pressure regulating structure that adjusts the flow into the engine and thus the output of the engine.

As we all know, JavaScript is a single-threaded job, which is already quite busy. It should not be blocked all the time, which can cause the browser to hang or even crash. Avoid frequent calls to expensive computational operations, such as DOM interactions.

Both anti-shake and throttling can optimize the efficiency of function execution and are very similar techniques. Add to this the fact that the throttle function in elevation is the same as the anti-shake function in understanding, so it can be confusing. When looking up the data, many people said that throttle function on the elevation was actually anti-jitter, and the function was named incorrectly. But to put it another way: Both anti-vibration and throttling are throttling technologies, and their basic idea is the same:

The basic idea behind function throttling is that some code cannot be executed continuously and repeatedly without a break. (JavaScript Advanced Programming)

In this case, there is no need to deliberately distinguish between anti-shake and throttling.

Function image stabilization

Only one call takes effect within a specified interval of n milliseconds.

Let’s simulate text input search:

function searchAjax(query) {
    console.log(`Results for "${query}"`);
}
document.getElementById("searchInput").addEventListener("input".function(event) {
    searchAjax(event.target.value);
});
Copy the code

Run the code above:

The input event invokes 15 search requests before the user input ends. In fact, only search requests for input events at the end of the character input are useful (especially for Chinese input methods), the rest are a waste of resources.

Let’s take a look at the effect of adding anti-shake:

function debounce(func, wait) {
    let timeId;
    return function(. args) {
    	let _this = this;
        clearTimeout(timeId);
        timeId = setTimeout(function() { func.apply(_this, args); }, wait); }}let searchDebounce = debounce(function(query) {
    console.log(`Results for "${query}"`);
}, 500);
document.getElementById("searchInput").addEventListener("input".function(event) {
    searchDebounce(event.target.value);
});
Copy the code

The running results are as follows:

Before the user finishes typing, search requests will no longer be called frequently, and only the most critical search for “pearl milk tea” will be retained.

The debounce function takes the searchAjax method and wraps it as a shock-proof searchDebounce method: Set the call delay to 0.5 seconds, and if searchDebounce is called again during the delay, it is delayed again by 0.5 seconds until there is a noticeable pause (searchDebounce is not called again) before the search request (searchAjax) is processed.

The function is delayed for n milliseconds after being called, and is re-timed if the function is called again during the wait.

Leading and trailing

In the text input search example, the search function is deferred. However, not all processing needs to be delayed. For example, submit button. Users prefer to complete the submission action in the first time and avoid repeated submission within a short period of time.

The function is executed immediately after it is called and is not repeated for the next n milliseconds. If the function is called again during the pause, the timer is reset.

Depending on the order in which the function is executed, there are two options: execution followed by wait is leading, and execution followed by wait is trailing.

Function of the throttle

Specifies that only one call to the function takes effect within n seconds.

Take scrolling content as an example:

Scroll events are triggered very frequently during page scrolling (a 500px scroll can trigger 100+ Scroll events), and if each Scroll event requires expensive computation, the whole scrolling experience can be mind-crushing.

Debounce also doesn’t work during infinite scrolling, because only an obvious pause debounce will handle scroll events. Users want scrolling to be smooth and unobtrusively, which requires us to constantly check to see if we need to load more content at a reasonable rate.

function throttle(func, wait) {
    let lastTime, deferTimer;
    return function(. args) {
        let _this = this;
        let currentTime = Date.now();
        if(! lastTime || currentTime >= lastTime + wait) { lastTime = currentTime; func.apply(_this, args); }else {
            clearTimeout(deferTimer);
            deferTimer = setTimeout(function() { lastTime = currentTime; func.apply(_this, args); }, wait); }}}function addContent() {/ *... * /}
$(document).on("scroll", throttle(addContent, 300));
Copy the code

Like debounce, Throttle encapsulates the addContent function as a function with throttling: set the delay to 0.3 seconds and call the func function every 0.3 seconds. If the call time condition is not met, use a timer to reserve a FUNc call for later, even if the call does not reach 0.3s.

Lodash source code interpretation

debounce

The source address

For ease of understanding, here is a simplified version:

function debounce(func, wait, options) {
  let lastArgs,
    lastThis,
    maxWait,
    result,
    timerId,
    lastCallTime

  let lastInvokeTime = 0
  let leading = false
  let maxing = false
  let trailing = true

  / / initialization
  if (typeoffunc ! = ='function') {
    throw new TypeError('Expected a function')
  }
  wait = +wait || 0
  if(isObject(options)) { leading = !! options.leading maxing ='maxWait' in options
    maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
    trailing = 'trailing' inoptions ? !!!!! options.trailing : trailing }/ / calls func
  function invokeFunc(time) {
    const args = lastArgs
    const thisArg = lastThis

    lastArgs = lastThis = undefined
    lastInvokeTime = time
    result = func.apply(thisArg, args)
    return result
  }

  // Start delay
  function startTimer(pendingFunc, wait) {
    return setTimeout(pendingFunc, wait)
  }

  // Before the delay starts
  function leadingEdge(time) {
    lastInvokeTime = time
    // Start delay
    timerId = startTimer(timerExpired, wait)
    // In leading mode, call func before delay
    return leading ? invokeFunc(time) : result
  }

  // Calculate the remaining delay time:
  MaxWait :(after last debouncedFunc call) the delay cannot exceed wait
  //2. MaxWait exists: func calls cannot be delayed beyond maxWait
  // Calculate the shortest time according to both cases
  function remainingWait(time) {
    const timeSinceLastCall = time - lastCallTime
    const timeSinceLastInvoke = time - lastInvokeTime
    const timeWaiting = wait - timeSinceLastCall

    return maxing
      ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
      : timeWaiting
  }

  // Check whether the current time can call func:
  //1. The first call to debouncedFunc
  //2. Wait milliseconds since last debouncedFunc call
  //3. The total delay of func calls reaches maxWait milliseconds
  //4. The system time is backward
  function shouldInvoke(time) {
    const timeSinceLastCall = time - lastCallTime
    const timeSinceLastInvoke = time - lastInvokeTime
    return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
      (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
  }

  // Delay timer callback
  function timerExpired() {
    const time = Date.now()
    // If the time condition is met, end the delay
    if (shouldInvoke(time)) {
      return trailingEdge(time)
    }
    // If the time condition is not met, calculate the remaining waiting time and continue to delay
    timerId = startTimer(timerExpired, remainingWait(time))
  }

  // After the delay ends
  function trailingEdge(time) {
    timerId = undefined
    // If it is trailing mode, call func
    if (trailing && lastArgs) {
      return invokeFunc(time)
    }
    lastArgs = lastThis = undefined
    return result
  }

  //debouncedFunc
  function debounced(. args) {
    const time = Date.now()
    const isInvoking = shouldInvoke(time)

    lastArgs = args
    lastThis = this
    lastCallTime = time

    if (isInvoking) {
      //timerId does not exist for two reasons:
      //1. First call
      //2. The last delayed call ends
      if (timerId === undefined) {
        return leadingEdge(lastCallTime)
      }
      // When there is a maximum delay limit for func calls, execute func and start the next delay, which can be throttle
      if (maxing) {
        timerId = startTimer(timerExpired, wait)
        return invokeFunc(lastCallTime)
      }
    }
    if (timerId === undefined) {
      timerId = startTimer(timerExpired, wait)
    }
    return result
  }
  return debounced
}

export default debounce
Copy the code

Tip: You can ignore maxWait for the moment when understanding debounce. The purpose of maxWait will be explained later.

How debouncedFunc works:

  1. Call for the first time

    Execute the leadingEdge function, leading true to call func before the delay, and then start the delay. The trailing option is true to call func after the end of the delay, and finally to end the process of a delayed func call.

  2. Call again

    If the last func delay call has ended, the leadingEdge function is executed again to start the delay process. Otherwise, the call is ignored. (If maxWait is set and the time condition for the call is currently met, call func immediately and start a new delayer)

If both the leading and trailing options are true, func can be called multiple times in a single anti-shake process.

Lodash adds the maxWait option to debounce, which specifies that func calls cannot be delayed longer than maxWait milliseconds, meaning that func must be called once per maxWait. So once maxWait is set, the effect is equivalent to function throttling. This can also be verified by lodash’s Throttle source code: Throttle’s wait is passed in as Debounce’s maxWait.

throttle

The source address

function throttle(func, wait, options) {
  let leading = true
  let trailing = true

  if (typeoffunc ! = ='function') {
    throw new TypeError('Expected a function')}if (isObject(options)) {
    leading = 'leading' inoptions ? !!!!! options.leading : leading trailing ='trailing' inoptions ? !!!!! options.trailing : trailing }return debounce(func, wait, {
    leading,
    trailing,
    'maxWait': wait
  })
}

export default throttle
Copy the code

References:

JavaScript Advanced Programming

Debouncing and Throttling Explained Through Examples

The Difference Between Throttling and Debouncing