The debounce function in LoDash is a timer that is used only once when an event is triggered several times within a specified period of time

The debounce function in Lodash takes three arguments: Func, wait, options 1, func is the function that needs to do the alarm processing 2, wait is how long to trigger 3, Options configuration item, with parameters such as maxwait- maximum wait time, finally return a debwait function, Debounce

function debounced(... args) { const time = Date.now() const isInvoking = shouldInvoke(time) lastArgs = args lastThis = this lastCallTime = time if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime) } if (maxing) { // Handle invocations in a tight loop. timerId = startTimer(timerExpired, wait) return invokeFunc(lastCallTime) } } if (timerId === undefined) { timerId = startTimer(timerExpired, wait) } return result }Copy the code

ShouldInvoke (); shouldInvoke ()

function shouldInvoke(time) {
  const timeSinceLastCall = time - lastCallTime
  const timeSinceLastInvoke = time - lastInvokeTime
  // Either this is the first call, activity has stopped and we're at the
  // trailing edge, the system time has gone backwards and we're treating
  // it as the trailing edge, or we've hit the 'maxWait' limit.
  return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
    (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
}
Copy the code

Look directly at the return result,

1, lastCallTime === undefined means the first trigger, because lastCallTime is undefined by default;These variables are defined at the beginning of the debounce function

TimeSinceLastCall >= Wait * * * * * * * * * * * * * * * * * * * * *

3, lastInvokeTime is assigned to lastCallTime in invokeFunc (parse invokeFunc function later), so timeSinceLastCall < 0 is almost impossible to appear in the first trigger, TimeSinceLastCall < 0 May appear when the computer time is adjusted;

4. If maxing is true, it means that the options parameter is passed in when the anti-shaking function is used, and the maxwait attribute is included in the options parameter. TimeSinceLastInvoke >= maxWait, lastInvokeTime = undefined, lastCallTime = lastCallTime So timeSinceLastInvoke >= maxWait also indicates that the interval between two triggers exceeds the maximum wait time

Therefore, when entering the debinvoking function for the first time, it will enter the if(isInvoking) condition. When triggering the timerId for the first time, it will be undefined because there is no value assigned during the definition, and the code will continue. The leadingEdge method is called and the lastCallTime parameter is passed. Now look at the leadingEdge method:

function leadingEdge(time) { // Reset any `maxWait` timer. lastInvokeTime = time // Start the timer for the trailing Edge. timerId = startTimer(timerExpired, wait) // Invoke the leading edge. Options. leading, otherwise, default to false // Assign result to return leading in invokeFunc? invokeFunc(time) : result }Copy the code

The simple logic is to assign lastInvokeTime to lastCallTime (that is, time, date.now ()) and timerId to startTimer(timerExpired, wait), Take a look inside the startTimer function:

function startTimer(pendingFunc, Wait) {if (useRAF) {/ / cancelAnimationFrame used to cancel plans to launch requestAnimationFrame method invocation request root of animation frames. CancelAnimationFrame (timerId) return root.requestAnimationFrame(pendingFunc) } return setTimeout(pendingFunc, wait) }Copy the code

At the beginning of the debounce function, useRef,

const useRAF = (! wait && wait ! == 0 && typeof root.requestAnimationFrame === 'function')Copy the code

Simple explanation is called decounce function, if not to wait, or to wait for 0, root requestAnimationFrame is a function (root as the object of the Window, So root. RequestAnimationFrame = = = ‘function’ indicates that the browser environment), the useRef to true;

RequestAnimationFrame and cancelAnimationFrame are two browser apis,

requestAnimationFrame Tell the browser that I need to execute an animation, and ask the browser to update the animation by calling the specified callback function before the next redraw. The purpose of this is that if wait is 0, the timer will not be delayed (we are not talking about macro and micro tasks here), namely setTimeout(callfunc,0), We all know that frame animation is at a minimum of 60 frames per second, and any lower than that you start to see stutter, or drop frames; Browsers also follow this rule, so a frame would be about 16 ms, namely the browser once every 16 ms redraw (now the browser may adjust according to the screen refresh rate), so even delay timer 0 s execution, the browser will not respond immediately, redrawn or have to wait to see the dom updates, belongs to macro task and timer, It is added to the macro task queue by the browser and follows the event loop mechanism, so the timer delay is usually higher than the specified delay time, so requestAnimationFrame is better than the timer. CancelAnimationFrame is the callback that cancels the requestAnimationFrame submission

Getting back to the fact that the timerId is assigned, look at the timerExpired function passed in when the startTimer function is called:

function timerExpired() {
    const time = Date.now()
    if (shouldInvoke(time)) {
      return trailingEdge(time)
    }
    // Restart the timer.
    timerId = startTimer(timerExpired, remainingWait(time))
}
Copy the code

We have already read shouldInvoke, which if true returns a new function trailingEdge(time), otherwise resetTimerID. There is another new function involved –remainingWait, Take a look at the trailingEdge function as follows:

function trailingEdge(time) {
    timerId = undefined

    // Only invoke if we have `lastArgs` which means `func` has been
    // debounced at least once.
    if (trailing && lastArgs) {
      return invokeFunc(time)
    }
    lastArgs = lastThis = undefined
    return result
}
Copy the code

To read its comment, it is only called when there is lastArgs, which means func has been buffered at least once. Return invokeFunc(time) if it is not invokeFunc(time), otherwise return result. Continue reading the invokeFunc function, which looks like this:

function invokeFunc(time) {
    const args = lastArgs
    const thisArg = lastThis

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

We will use apply to change the reference to this on the func call and assign the result to result

Look at the remainingWait function, which looks like this:

Function remainingWait(time) {// Assign the current time to lastCallTime in debounce const timeSinceLastCall = time-lastCallTime // LastInvokeTime defaults to 0 and is assigned to time halfway through, Const timeSinceLastInvoke = time-lastInvokeTime const timeWaiting = wait-timesincElastCall Return Maxing? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting }Copy the code

Maxing = ‘maxWait’ in options, timeWaiting = wait-timesincElastCall; maxing = ‘maxWait’ in options, timeWaiting = wait-timesincElastCall; TimeSinceLastCall = time-lastCallTime; Time is the date.now () taken from the new time when executing the timerExpired function, and lastCallTime is the date.now () taken from the debwriting function when executing the trigger function (it will be reassigned every time), so there will be a time difference between the two. TimeSinceLastCall is simply the time it takes the code to execute until now. TimeSinceLastInvoke = time – lastInvokeTime lastInvokeTime is in invokeFunc function for the assignment, and invokeFunc function performs, That is, when setTimeout will be executed or when the requestAnimationFrame callback will be executed, but the time will be passed in at the beginning of the debwriting, so the value of lastInvokeTime will be the timestamp of the debwriting function for the first time. LastInvokeTime is not reassigned when it is triggered later, until func is executed for the first time, after the wait time has elapsed. So the value of timeSinceLastInvoke represents the time difference between when the function was first triggered and now

Return to leadingEdge, which returns return leading? InvokeFunc (time) : result, leading default value is false, then set to!! Options. Leading. InvokeFunc () {if options does not pass the leading parameter, result is undefined, otherwise result is set to func.apply(…);

By now, the situation of not passing options when calling debounce has been explained and the function involved has also been explained. For the next condition of debounce function, please refer to the following flow chart:The main one is the timerExpired passed in startTimer, because the timerExpired method is directly added to the timer, whereas in the timerExpired method, the result of the trailingEdge function is returned when the condition is met. In the invokeFunc function, func. Apply (thisArg args) is executed. At this point, the function passed in (Russian doll-like feeling) is executed to achieve the purpose of anti-shock