Why do you want to write this article?
You’ve read a lot of articles about throttling/stabilization, most of which are split into immediate and non-immediate versions. In recent use of the process, I found that both versions of the input box when the input results are not ideal. Let’s take a look at the corresponding scenario and how to optimize it.
Ii. Usage Scenarios
1. Not immediately executed
The non-immediate version is that the event is executed only once, at the end, as long as the input interval does not exceed the set interval.
-
The corresponding code is:
/** * Not immediately executable version *@author waldon
* @date The 2021-05-01 *@returns {Function} - Anti-shaking function */
const debounce = (function () {
let timer = 0
return function (fn, delay = 300) {
if (timer) {
clearTimeout(timer)
}
// Not immediately executed
timer = setTimeout(() = > {
fn()
}, delay)
}
})()
Copy the code
-
Effect display:
-
defects
As you can see from the GIF above, it takes 300 milliseconds for the event to execute after the input value is changed. However, the v-Model of Vue uses compositionStart and compositionend to help us optimize the Chinese input. The general user will think of seeing the corresponding search results when typing the first word. This stabilization sacrifices the user experience in terms of speed.
2. Execute immediately
The immediate execution version is the event that only triggers the first keyword change during continuous input. It will not be triggered again unless the interval entered later is greater than the set interval
-
The corresponding code is:
/** * Instant execution version *@author waldon
* @date The 2021-05-01 *@returns {Function} - Anti-shaking function */
const debounce = (function () {
let timer = 0
return function (fn, delay = 300, immediate = true) {
if (timer) {
clearTimeout(timer)
}
if (immediate) {
constcallNow = ! timer timer =setTimeout(() = > {
timer = 0
}, delay)
if (callNow) {
fn()
}
} else {
// Not immediately executed
timer = setTimeout(() = > {
fn()
}, delay)
}
}
})()
Copy the code
-
Effect display:
-
defects
This instant stabilization is buggy when used in input boxes.
- When the input is fast, only the first keyword will be searched and the rest will be ignored. If used in the search list, this search result is definitely wrong
- Long press back/ Delete key delete, frequency is also very fast. The search results that delete the first keyword are still displayed even after the deletion.
At this time, a careful friend will think of. Why not trigger once at the beginning and end of the input? Let’s move on to the third one.
3. Execute immediately + Delay
The effect of this is a combination of the first two. The logic will be executed once when the first key word is entered, and then the logic will not be executed if continuous input is entered in the middle. When the input stops, the logic of the last input is executed again.
-
The corresponding code is:
/** * Repeat the execution version *@author waldon
* @date The 2021-05-01 *@returns {Function} - Anti-shaking function */
const debounce = (function () {
let timer = 0
return function (fn, delay = 300, immediate = true) {
if (timer) {
clearTimeout(timer)
}
if (immediate) {
constcallNow = ! timer timer =setTimeout(() = > {
fn() // This step is more than the immediate execution version
timer = 0
}, delay)
if (callNow) {
fn()
}
} else {
// Not immediately executed
timer = setTimeout(() = > {
fn()
}, delay)
}
}
})()
Copy the code
-
Effect display:
-
review
Delays and inaccurate results have been addressed in this release. But more attentive friends might have noticed: “In the GIF above, only one keyword was entered, and it was executed twice.” If there is no logic in the project to handle repeated requests, wouldn’t there be two repeated requests? That definitely needs to be optimized.
4. Execute now + Delay + cacheKey
This has the same effect as the third version, except that a cacheKey field is added to determine whether the value entered last time is the same as the value entered last time, avoiding repeated logic.
-
The corresponding code is:
/** * cacheKey version *@author waldon
* @date The 2021-05-01 *@returns {Function} - Anti-shaking function */
const debounce = (function () {
let timer = 0
let cacheKey = ' '
return function (fn, delay = 300, immediate = true, key = ' ') {
if (timer) {
clearTimeout(timer)
}
if (immediate) {
// Execute immediately
letcallNow = ! timer timer =setTimeout(() = > {
timer = 0
if(cacheKey ! == key) { fn() } }, delay)if (callNow) {
cacheKey = key
fn()
}
} else {
// Not immediately executed
timer = setTimeout(() = > {
fn()
}, delay)
}
}
})()
Copy the code
-
Effect display:
-
review
Solved the problem of repeated execution of the same keyword. We can use the input value as the key in the input event, and the scrollTop value as the key in the pageScroll event. In fact, this can handle most scenarios, but some of the more special scenarios, when he triggered, no change value, then this definitely does not apply.
5. Execute now + Delay + lastTimer
This version is actually based on the source code of Lodash. The general idea is to assign the task pool ID to another variable the first time a scheduled task is defined. The timer is going to keep changing over time, but the lastTimer that was assigned at the beginning is not going to change. If the two values are not consistent, the callback function is not fired. After the continuous firing stops, the variables are reset in the callback function.
-
The corresponding code is:
/** * stabler function lastTimer version *@author waldon
* @date The 2021-05-01 *@returns {Function} - Anti-shaking function */
const debounce = (function () {
let timer = 0
let lastTimer = 0
return function (fn, delay = 300, immediate = true) {
if (timer) {
clearTimeout(timer)
}
if (immediate) {
// Execute immediately
letcallNow = ! timer timer =setTimeout(() = > {
if(lastTimer ! == timer) { timer =0
lastTimer = 0
fn()
}
}, delay)
if (callNow) {
lastTimer = timer
fn()
}
} else {
// Not immediately executed
timer = setTimeout(() = > {
fn()
timer = 0
}, delay)
}
}
})()
Copy the code
-
Effect display:
The effect here is the same as in version 4, no duplicate textures.
-
Lodash debounce implementation source code
function debounce(func, wait, options) {
let lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime
let lastInvokeTime = 0
let leading = false
let maxing = false
let trailing = true
// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
constuseRAF = (! wait && wait ! = =0 && typeof root.requestAnimationFrame === 'function')
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 }function invokeFunc(time) {
const args = lastArgs
const thisArg = lastThis
lastArgs = lastThis = undefined
lastInvokeTime = time
result = func.apply(thisArg, args)
return result
}
function startTimer(pendingFunc, wait) {
if (useRAF) {
root.cancelAnimationFrame(timerId)
return root.requestAnimationFrame(pendingFunc)
}
return setTimeout(pendingFunc, wait)
}
function cancelTimer(id) {
if (useRAF) {
return root.cancelAnimationFrame(id)
}
clearTimeout(id)
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time
// Start the timer for the trailing edge.
timerId = startTimer(timerExpired, wait)
// Invoke the leading edge.
return leading ? invokeFunc(time) : result
}
function remainingWait(time) {
const timeSinceLastCall = time - lastCallTime
const timeSinceLastInvoke = time - lastInvokeTime
const timeWaiting = wait - timeSinceLastCall
return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting
}
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))
}
function timerExpired() {
const time = Date.now()
if (shouldInvoke(time)) {
return trailingEdge(time)
}
// Restart the timer.
timerId = startTimer(timerExpired, remainingWait(time))
}
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
}
function cancel() {
if(timerId ! = =undefined) {
cancelTimer(timerId)
}
lastInvokeTime = 0
lastArgs = lastCallTime = lastThis = timerId = undefined
}
function flush() {
return timerId === undefined ? result : trailingEdge(Date.now())
}
function pending() {
returntimerId ! = =undefined
}
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
}
debounced.cancel = cancel
debounced.flush = flush
debounced.pending = pending
return debounced
}
Copy the code
Third, summary
The usage scenario here is for a very large number of users or requests that are very performance intensive. If server stress permits, the user experience will be better if throttling is used to give feedback at appropriate intervals.
Finally, May Day happy ~
Iv. Reference Resources
- Github.com/lodash/loda…