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