This is the 117th original article without water, if you want to get more original good articles, please search our official account to follow us. This article was first published in the front blog of political Business Cloud: Anti-shake throttling scenarios and applications
Anti-shake throttling scenario and application
background
In daily development, we often encounter search queries that trigger changes in Input values and continue to trigger function calls as the user enters. Or if we listen for window scrolling to send a buried request while the user is swiping through an item on the product search page, the interface calls will be triggered frequently. But sometimes we don’t want the interface to be invoked frequently during the user’s continuous operation. In order to limit the occurrence of such high-frequency function calls in a short time, we can use anti-shake and throttling.
Function stabilization and throttling optimize function execution efficiency by controlling event trigger frequency. Let’s take a visual look at the difference between conventional, anti-shake and throttling through the following figure.
The horizontal axis in the figure is the time axis. It can be seen that the anti-shake method is to execute the callback after the event stops firing for a period of time, and the throttling method is to execute the callback at a certain time interval when the event continues firing.
Analysis of the anti-shake and throttling scenarios
Image stabilization
Anti-shake, as the name suggests, to prevent shaking. It is used to convert user action trigger to program action trigger to prevent jitter of user operation results. The callback is executed only once in a period of time when the event is executed multiple times within a specified interval of n seconds.
Features: Wait for an operation to stop, then interval the operation
- Continuous trigger non-execution
- Does not trigger for a period of time before executing
Application Scenarios:
Mousemove Mouse slide event
// It is not executed immediately for the first time
function debounce(func, wait) {
let timer;
return function () {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(function(){ func.apply(context, args) }, wait); }}function getUserAction(e) {
// container is a sample code container
container.innerHTML = `${e.clientX}.${e.clientY}`;
};
container.onmousemove = debounce(getUserAction, 1000);
Copy the code
The anti-shake effect is not added as shown in the figure. The x and Y coordinates are constantly changing in the process of mouse sliding.
As shown in the picture after anti-shake effect is added, x and Y axis coordinates are updated 1000ms after the mouse stops sliding.
Let’s take a look at a common business example, Select to the server’s dynamic search function. The difference with the above scenario is whether it is executed the first time
// Execute the first function immediately, show the user the default m pieces of data, wait until the user manually input to stop triggering n seconds, then execute again
function debounce(func, wait, immediate) {
let timer;
let localImmediate = immediate;
return function () {
const context = this;
const args = arguments;
if (localImmediate) {
// The flag is to indicate whether the first execution is immediate
localImmediate = false;
func.apply(context, args);
}
clearTimeout(timer);
timer = setTimeout(function(){ func.apply(context, args) }, wait); }}function fetchData(vaule) {
// Call the interface to request data
}
debounce(fetchData, n);
Copy the code
-
The above is realized through the anti-shake method, after the user stops input for n seconds, the user goes to the server to request data, but it may trigger the first search after the user enters Hangzhou. The city stadium was then entered, triggering a second search.
There are two possible scenarios displayed on the page:
- The first result returns faster than the second and is displayed first
hangzhou
Search results, and then displayHangzhou Gymnasium
Search results. The effect is as follows: - The first result returns slower than the second and is displayed first
Hangzhou Gymnasium
Search results, and then displayhangzhou
Search results. The effect is as follows:
In fact, neither the first case nor the second case is very good, we hope that it will directly display a hangzhou stadium search results.
So how to deal with it? We can simply set a variable to mark the last request and only show the result to the user if the token requested by the current interface is equal to the latest token.
- The first result returns faster than the second and is displayed first
/** * Update the global variable this.lastFetchId and assign the value to the internal variable fetchId each time the fetchData method is called. * The internal logic of each fetchData method determines whether the internal variable fetchId is equal to the global variable * this.lastFetchId after the interface returns successfully. * /
// Global variable that marks the latest request ID, updated each time fetchData is called
this.lastFetchId = 0;
function fetchData(value) {
const { searchField, params = {}, url, dataKey } = this.props;
const [data, setData] = useState([]);
const [fetching, setFetching] = useState(false);
this.lastFetchId += 1;
// The internal variable fetchId for each method call
const fetchId = this.lastFetchId;
setData([]);
setFetching(true);
const postValue = typeof value === 'string' ? value : ' ';
params[searchField] = postValue;
request(url, {
method: 'post'.data: params,
}).then((res) = > {
const { success, result } = res || {};
// If it is not the latest request, no result assignment is performed
if(fetchId ! = =this.lastFetchId) {
return;
}
if (success && Array.isArray(result)) {
setData(result);
setFetching(false); }}); };Copy the code
As can be seen from the above examples, the anti-shake method avoids mistaking an operation as multiple operations, and limits the upper limit of event execution, that is, to execute the event n seconds after the trigger is stopped. The same scenario may include login, registration and other form submission operations, such as multiple requests triggered by users clicking too fast, and real-time saving of editing content such as rich text editor emails.
The throttle
Throttling, as the name suggests, controls flow. It is used to control the frequency of events when the user interacts with the page. In general, the operation is performed periodically in a unit of time or other intervals. For a period of time, the event fires once every time it reaches our specified interval of n seconds.
Features: After waiting for a certain interval, the operation
- Continuous triggering does not occur more than once
- To a certain time/other interval (such as sliding height) to execute
Application scenarios (Note: The following examples are related to the business content of the company, so the actual screen shots are not displayed) :
- Buried scene. Commodity search list, commodity window, etc., the user sliding time/fixed sliding height to send buried point request
If the event is executed for the first time after n seconds, it will be executed again after the event stops triggering
// If the trigger stops at 6.8s, then the trigger is executed at 6s.
// The execution will continue for the last time at 7s
function throttle(func, wait) {
var timer;
return function() {
var context = this;
var args = arguments;
if(! timer) { timer =setTimeout(function(){
timer = null;
func.apply(context, args)
}, wait)
}
}
}
function sendData(vaule) {
// Call the interface to send data
}
throttle(sendData, n);
Copy the code
As shown, buried requests are sent at fixed intervals
- The O&M system refreshes application run logs every n seconds
If the event is executed for the first time after n seconds, the event will not be executed again after it stops triggering
If the trigger stops on 6.8s, the last time will be executed on 6s, after which it will not be executed again
function throttle(func, wait) {
var previous = 0;
return function() {
// Implicit conversion
var now = +new Date(a);var context = this;
var args = arguments;
if(now - previous > wait) { previous = now; func.apply(context, args); }}}function fetchLogData(vaule) {
// Call the interface to get log data
}
throttle(fetchLogData, n);
Copy the code
As shown, pull run logs at fixed intervals
As you can see from the above example, throttling controls the frequency of event firing and limits the upper and lower limits of event execution, that is, every n seconds during event firing. The same scenario might include more frequently triggered events such as Scroll Mousemove, browser progress bar location calculation, input dynamic search, etc.
Lodash anti – throttling source code analysis
The basic implementation and application scenarios of anti-shake throttling are introduced above for our understanding and use. In actual business scenarios, we will choose mature third-party libraries to achieve the effect of shaking prevention and throttling. We take a look at Lodash and Underscore. Js and so on.
Anti-shock: The core idea of Lodash is not to frequently manage the timer, but to implement shouldInvoke to determine if func should be executed, and to cancel the timer only when the provided cancel method cances the delay.
The internal implementation logic of shouldInvoke, which is described in detail in the function execution module below, is called in timer switches and entry functions to determine whether a func function should be executed. We can see some of the comments in the source code below. We disassemble into four modules for analysis: basic definition, timer switch, function execution, external callback.
Basic Definition (including overall structure)
The following is the overall code structure for Lodash to implement shock protection. The entry function defines some timers and function execution variables. There are 10 variables in total, among which 7 time-related variables, maxWait, timerId, lastCallTime, lastInvokeTime, leading, Maxing and trailing, are important support for the realization of timer switch and function execution module.
import isObject from './isObject.js'
import root from './.internal/root.js'
function debounce(func, wait, options) {
/** ====== Basic definition ====== */
let lastArgs, // Last time I will execute the arguments of debmentioning
lastThis, // Last time this
maxWait, // Maximum wait time, ensure that greater than the set maximum interval will be executed, for throttling effect
result, // The return value of the function func
timerId, // Timer ID
lastCallTime // The last time debounce was called
let lastInvokeTime = 0 // The last time func was executed for throttling effect
let leading = false // First trigger before delay
let maxing = false // Whether maxWait is set to implement throttling effect
let trailing = true // Last trigger after delay
// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
constuseRAF = (! wait && wait ! = =0 && typeof root.requestAnimationFrame === 'function')
if (typeoffunc ! = ='function') {
throw new TypeError('Expected a function')}// Implicit conversion
wait = +wait || 0
* function isObject(value) {* const type = typeof value * return value! = null && (type == 'object' || type == 'function') * } */
if(isObject(options)) { leading = !! options.leading maxing ='maxWait' in options
// maxWait takes the maximum value of maxWait and wait. To implement throttling, ensure that maxWait is greater than wait
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
trailing = 'trailing' inoptions ? !!!!! options.trailing : trailing }/** ====== Timer switch ====== */
// Set the timer
function startTimer(pendingFunc, wait) {}
// Cancel the timer
function cancelTimer(id) {}
// Calculate the waiting time
function remainingWait(time) {}
// Timer callback
function timerExpired() {}
/** ====== executes ====== */
/ / delay before
function leadingEdge(time) {}
// Callback after delay
function trailingEdge(time) {}
// Execute the func function
function invokeFunc(time) {}
// Determine whether the func function should be executed at this point
function shouldInvoke(time) {}
/** ====== external callback ====== */
// Cancel the delay
function cancel() {}
// Call immediately
function flush() {}
// Check whether the timer is in
function pending() {}
// the entry function
function debounced(. args) {}
debounced.cancel = cancel
debounced.flush = flush
debounced.pending = pending
return debounced
}
export default debounce
Copy the code
Timer switch
/** ====== Timer switch ====== */
// Set the timer
function startTimer(pendingFunc, wait) {
if (useRAF) {
/ / not Settings set to wait or wait for 0 at the window call. RequestAnimationFrame ().
// Requires the browser to call the specified callback function to update the animation before the next redraw
root.cancelAnimationFrame(timerId)
return root.requestAnimationFrame(pendingFunc)
}
return setTimeout(pendingFunc, wait)
}
// Cancel the timer
function cancelTimer(id) {
if (useRAF) {
return root.cancelAnimationFrame(id)
}
clearTimeout(id)
}
// Calculate the waiting time
function remainingWait(time) {
// The interval between the current time and the last call to debounce
const timeSinceLastCall = time - lastCallTime
// The interval between the current time and the last func execution
const timeSinceLastInvoke = time - lastInvokeTime
// Remaining waiting time
const timeWaiting = wait - timeSinceLastCall
// Whether the maximum wait time is set (whether throttling is set)
// No: remaining waiting time
// Yes: the remaining wait time and the minimum interval between the current time and the last func execution
return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting
}
// Timer callback
function timerExpired() {
const time = Date.now()
// Perform postponement callback when func should be executed
if (shouldInvoke(time)) {
return trailingEdge(time)
}
// Calculate the waiting time and reset the timer
timerId = startTimer(timerExpired, remainingWait(time))
}
Copy the code
Function performs
/** ====== executes ====== */
/ / delay before
function leadingEdge(time) {
// Sets the last time the func function was executed
lastInvokeTime = time
// Set the timer
timerId = startTimer(timerExpired, wait)
// Execute func immediately if leading is set
return leading ? invokeFunc(time) : result
}
// Callback after delay
function trailingEdge(time) {
timerId = undefined
// trailing continues to trigger once after the trailing delay
// lastArgs marks debounce as having been executed at least once
if (trailing && lastArgs) {
return invokeFunc(time)
}
// Reset parameters
lastArgs = lastThis = undefined
return result
}
// Execute the func function
function invokeFunc(time) {
const args = lastArgs
const thisArg = lastThis
lastArgs = lastThis = undefined
lastInvokeTime = time
result = func.apply(thisArg, args)
return result
}
// Determine whether the func function should be executed at this point
function shouldInvoke(time) {
// The interval between the current time and the last call to debounce
const timeSinceLastCall = time - lastCallTime
// The interval between the current time and the last func execution
const timeSinceLastInvoke = time - lastInvokeTime
// First call
// The wait interval is exceeded
// The system time has changed
// The maximum waiting time maxWait is exceeded
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
}
Copy the code
Foreign callback
/** ====== external callback ====== */
// Cancel the delay
function cancel() {
// Cancel the timer
if(timerId ! = =undefined) {
cancelTimer(timerId)
}
// Reset parameters
lastInvokeTime = 0
lastArgs = lastCallTime = lastThis = timerId = undefined
}
// Call immediately
function flush() {
return timerId === undefined ? result : trailingEdge(Date.now())
}
// Check whether the timer is in
function pending() {
returntimerId ! = =undefined
}
Copy the code
Throttling: The implementation of the throttling function in Lodash is simple. It directly calls the anti-shake function and sets the input parameter maxWait to achieve throttling effect.
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
The above is a brief analysis of the anti-shake and throttling implementation of Lodash. In actual business scenarios, you can use the anti-shake and throttling methods provided by Lodash directly. If you need more customized functions that may not be implemented or do not support configuration, you can consider implementing them based on your understanding of the source code to meet actual business requirements.
The difference between anti-shake and throttling depends on the actual service scenario
Visual comparison, online view (note: from Stuart – function tremble and function throttling)
Anti-shake may be used for unpredictable user initiatives, such as users entering content to dynamically search for results on the server. The speed at which users type is unpredictable and irregular.
Throttling may be used for some non-user active behaviors or predictable user active behaviors, such as sending buried request when users slide merchandise window, sliding fixed height is known logic, with regularity.
conclusion
Using the idea of anti-shake and throttling, to control the timing of function execution, can save performance, avoid page lag and other bad user experience. The concepts of anti-shake and throttling are similar and difficult to distinguish. The above content has been introduced from the perspective of the author’s own understanding of anti-shake and throttling.
While beginners may see a different understanding and introduction in JavaScript Advanced Programming or some other technical article by the author, You might see arguments and arguments that throttle in JavaScript Advanced Programming is debounce, that dynamic search should be defused, and real-time search should be throttled. We hope that after you have your own understanding of anti-shake and throttling, you can decide to use anti-shake and throttling according to the actual application scenarios and requirements, and choose a more reasonable and appropriate method.
reference
Stuart – function anti – shake and function throttling
Lodash anti – shake throttling source code understanding
Function anti – shake and throttle is what??
Underscore for JavaScript topics
JavaScript topics follow the science of underscore
How are Lodash anti-shake and throttling implemented
Recommended reading
E-commerce minimum inventory – SKU and algorithm implementation
What you need to know about project management
How to build code global retrieval system from 0 to 1
How to build a build deployment platform suitable for your team
Open source works
- Political cloud front-end tabloid
Open source address www.zoo.team/openweekly/ (wechat communication group on the official website of tabloid)
, recruiting
ZooTeam, a young passionate and creative front-end team, belongs to the PRODUCT R&D department of ZooTeam, based in picturesque Hangzhou. The team now has more than 50 front-end partners, with an average age of 27, and nearly 30% of them are full-stack engineers, no problem in the youth storm group. The members consist of “old” soldiers from Alibaba and netease, as well as fresh graduates from Zhejiang University, University of Science and Technology of China, Hangzhou Electric And other universities. In addition to daily business docking, the team also carried out technical exploration and practice in material system, engineering platform, building platform, performance experience, cloud application, data analysis and visualization, promoted and implemented a series of internal technical products, and continued to explore the new boundary of front-end technology system.
If you want to change what’s been bothering you, you want to start bothering you. If you want to change, you’ve been told you need more ideas, but you don’t have a solution. If you want change, you have the power to make it happen, but you don’t need it. If you want to change what you want to accomplish, you need a team to support you, but you don’t have the position to lead people. If you want to change the pace, it will be “5 years and 3 years of experience”; If you want to change the original savvy is good, but there is always a layer of fuzzy window… If you believe in the power of believing, believing that ordinary people can achieve extraordinary things, believing that you can meet a better version of yourself. If you want to be a part of the process of growing a front end team with deep business understanding, sound technology systems, technology value creation, and impact spillover as your business takes off, I think we should talk. Any time, waiting for you to write something and send it to [email protected]