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)
- The mouse (
-
Execute the callback after a delay of milliseconds when debounce is not called again, for example
- in
mousemove
Event to ensure that the listener function is called only once for multiple triggers - In the form verification, do not add shake – proof, enter in turn
user
, will be divided intou
.us
.use
.user
Send requests four times; And add anti – shake, set a good time, you can achieve complete inputuser
Before a verification request is issued
- in
2. The train of thought
-
The debounce function accepts at least two arguments (popular libraries have three arguments)
- The callback function
fn
- Delay time
delay
- The callback function
-
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 closures
fn
To 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 time
delay
None 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
- Implement in delay time
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 (underscore
The implementation of the)
-
Execute immediately. Add a third parameter, two cases
- The callback function is executed first
fn
After the trigger has stoppeddelay
Milliseconds before it can be triggered again (To perform first) - Successive calls to debounce do not trigger the callback and stop the call after
delay
Milliseconds before executing the callback function (After the implementation) clearTimeout(timer)
Later,timer
It doesn’t becomenull
Instead, 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
- The callback function is executed first
-
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 executed
undefined
- In the first case, the return value is returned first
- 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 executed
- Can cancel the debounce function. Generally when
immediate
fortrue
Trigger once and waitdelay
Time, 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
- A callback may have a return value.
-
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, listening
mousemove
Event, 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
- For example, listening
-
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)
previous
Is 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
- The listener event is triggered, and the callback function executes immediately (initial)
-
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 }
- Ignore the call on the start boundary, passed in
-
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: false
Cannot be set at the same time- The first start boundary is not executed, but the first time it fires,
previous
To 0, theremaining
Values andwait
The same. So,if (! previous && options.leading === false)
True, changedprevious
The value of andif (remaining <= 0 || remaining > wait)
Is false - Triggering it in the future will cause
if (! previous && options.leading === false)
Is false, andif (remaining <= 0 || remaining > wait)
Is true. It becomes the start boundary execution. So you andleading: false
The conflict
- The first start boundary is not executed, but the first time it fires,
- There’s a question,
📚 summary
- At this point, one is fully implemented
underscore
The Debounce and Throttle functions in - while
lodash
The 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
throttle
Strategy: 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,debounce
Strategy: 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