Stabilization and throttling everyone should not unfamiliar, as a performance optimization (can be the response performance of the front page, also can reduce the server pressure) is a necessary skill, we said to have a good talk about everything, and also in line with one day a small goal, step by step understand a knowledge point, today, we have to fine chat under the black magic of stabilization and throttling.

This article will start with the basic version of the anti-shake and throttling, and gradually develop a version that can be used in most scenarios. Some EventLoop concepts may be involved in the process, but we won’t go into details. An article on EventLoop will be published later for further study.

1. Debounce

The application scenario of anti-shake is much more common than throttling. For example, many Web pages will have such a search box: for fuzzy search of specified content, let’s take Baidu search as an example:

The function of this search box is also very simple, that is, when you enter content in the search box, you will call the interface to transfer the content to the server for content search, then we will implement this function:

. const API = XXX const input = document.documentElement.getElementById('keyWord') input.oninput = function (e) { const keyWord = e.target.value API.request({ keyWord }) ... }Copy the code

Soon we were done with this feature, but! There is a serious problem when you type quickly into the search box: the interface is called too often.

And most of these calls are meaningless, because when the user quickly and continuously in the process of the input is not concerned about the feedback content, because he’s still in the type he want to search keywords, and only after the appropriate pause, we would think this content may be he want to search keywords, but by this time the request is valuable.

Therefore, the feature or implementation that can “intelligently” and “timely” do the “send request” thing is called “anti-shake”.

Anti-shake, in the continuous uninterrupted call, the program will not immediately respond to the call within a certain threshold range, but will only respond to the call when the time between a call and the last call exceeds the threshold.

A picture would look something like this:

Let’s take a look at the Baidu search box in the fast input content yes network request:

In the red box, baidu uses “anti-shake” technology to avoid unnecessary requests. Take a closer look at the onInput event bound to the search box:

startCircle: function() {
    var e = this;
    e.timer || ($(e.ipt).trigger("start", [e]),
    e.timer = setTimeout(function() {
        e.check(),
        e.timer = setTimeout(arguments.callee, 200)
    }, 200),
    supportInputEvent && $(e.ipt).bind("input", function() {
        e.check()
    }))
},
stopCircle: function() {
    var e = this;
    e.timer && (clearTimeout(e.timer),
    supportInputEvent && $(e.ipt).unbind("input"),
    e.timer = null,
    $(e.ipt).trigger("stop", [e]))
}
Copy the code

This is only the partial implementation code of the input box responding to the event of the change of input content, but we can still see the hint at a sight, that is, the proper organization of setTimeout and clearTimeout can realize the “anti-shake” function, which is implemented in our own code:

@param {delay} Function {delay} Function (ms) */ Function debounce (fn, delay=300) { let timer = null; return function(... Args) {// Each time a new function call is triggered, the previous delayed call task is cleared, Timer && clearTimeout(timer) timer = setTimeout(() => {fn.apply(this, args) timer = null}, delay)}}Copy the code

The code above is pretty much what we need, but if you think about it, it has one drawback: even if it is called once, the call will only respond after a delay time, which does not cover all application scenarios.

Therefore, we can further refine its functions and features so that it can support passing parameters to configure whether to respond immediately to the first call:

@param {method} Function Specifies the Function that needs to be called * @param {delay} Function Specifies the time threshold (ms) * @param {immediate} Boolean Whether execution is required immediately (i.e., at the head of a threshold range, if it is within the previous threshold range when triggered, */ function debounceX (method, delay, immediate) {let timer = null return function(... Args) {// Each time you enter, you need to clear an infinite delay in executing the task (if any) and then divide the strategy into different scenarios. Timer && clearTimeout(timer) // Execute at the head of a threshold if (immediate) {// Ensure that only the first trigger is "executed immediately" in this cycle. Each subsequent trigger needs to update the scheduled task delay trigger. // Also to ensure that this cycle is over, Immediate = false timer = setTimeout(() => {timer = null immediate = true}, Delay) // Execute method.apply(this, Timer = setTimeout(() => {timer = null method.apply(this, args)}, delay)}}}Copy the code

At this point, we have perfectly implemented a “shake off” function that works in most scenarios, and we can now add our own search box function:

function search (e) {
    const keyWord = e.target.value
    API.request({
        keyWord
    })
    ...
}
input.oninput = debounceX(search, 200)
Copy the code

Second, Throttle and Throttle

For throttling, the most common scenario in A Web development effort is to respond to actions such as animation and scrolling on some page.

Take the most common example: there is some module A on the page, when A scroll to and beyond the top of the page needs to “top” display (i.e. fixed at the top of the page). The natural way to write this feature is:

window.onsrcoll = function (e) {
    const nodeA = document.documentElement.getElementById('A')
    const rect = nodeA.getBoundingClientRect() 
    const top = rect.top
    if (top < 0) {
        nodeA.classList.add('fixed')
    } else {
        nodeA.classList.remove('fixed')
    }
}
Copy the code

If we were to write our code this way, we would find that it would execute the event callback function as frequently as the search box example we originally wrote.

So can we use Debounce to solve the problem? The answer is no. If you don’t believe me, you can try it. You will find that when you scroll the page quickly and scroll the module A above the top of the page, it will “top” after delay, and then when you scroll into the page quickly, it will change to “top” after delay.

As it turns out, this is a scenario that can’t be solved using Debounce, and we’re going to use throttle, as well as resize, Mousemove, and so on.

These events have two characteristics in common, namely:

  1. Trigger events continuously, but many triggers are invalid, such as the trigger frequency exceeding the screen refresh rate (60hz, that is, the time interval between two triggers is less than16ms);
  2. The real-time response to events has high requirements, but it exceeds the screen refresh rate (60hz) is also invalid.

If we want to meet the 60Hz hardware specification, we need to use the API provided by the browser requestAnimationFrame, but this is not the focus of this article, we just delve into the scenario. But the core feature remains the same, namely that these events generate a lot of invalid operations that should be avoided. At this point, the throttling function comes into being.

Throttling. In continuous uninterrupted calls, the program responds to calls only once, at a fixed frequency, within a set threshold.

A picture would look something like this:

The simplest way to implement this in code is:

@param {delay} Function throttling (ms) */ Function throttle (fn, delay=300) { let startTime = Date.now(); let timer = null; return function (... Args) {const remaining = delay - (date.now () -starttime) if (remaining <= 0) {// When remaining time is less than 0, the remaining time of the previous round must be clear. To avoid the delayed task of the last round not being executed on time due to various reasons, If not cleared it may result in timer && clearTimeout(timer) method.apply(this, args) startTime = Date.now() } else if (timer === null) { timer = setTimeout(() => { method.apply(this, Args) startTime = date.now () // Must be set to null to ensure that the next call can enter the else if branch timer = null}, remaining)}}}Copy the code

Such a throttle-down capability is sufficient for most application scenarios, but there are many excellent JS libraries in the community that implement “throttling capabilities” that can be arbitrarily configured with behavioral features such as underscore, Lodash, and so on.

For example, throttle in underscore, we break down the implementation principle and design idea row by row:

/** * Upgraded version (configurable event trigger execution mode: 'head triggering events' | |' head triggering event) from the underscore * @ param * @ {*} method callback param * @ {*} delay delay threshold param {*} options if you want to set 'head triggering event, Pass {leading: false}, if you set 'header trigger event', pass {trailing: false}. * Both cannot be set to false, */ function throttle (method, delay=300, options={}) {let timer = null // previous // if options.leading! The value of previous is 0, and the value of now is a timestamp, which means remaining = delay - (now-previous) is a negative value less than 0. Return function () {let self = this let args = array. from(arguments) let now = date.now () // ! Previous is valid only in two cases: // 1. // 2. Non-first trigger event, and options.leading === false. // Both scenarios indicate the end of one execution cycle and can proceed to the next execution cycle. if (! Previous && options.leading === false) {previous = now} // Remaining time const remaining = delay - (now-previous) // *** ** : // 1. It can also avoid setting "tail execution", And delay the task because the task queue 】 【 queuing problems that may occur without the problem of "punctual execution" if (remaining < = 0 | | remaining > delay) {if (timer) {/ / to keep clear of in time the task without "perform" on time delay clearTimeout(timer) timer = null } previous = now method.apply(self, args) if (! timer) self = args = null // ! The default timer value is null (leading = false) // 2. Non-first trigger event, and one execution cycle has already ended (both direct trigger callback and delayed task trigger callback)} else if (! timer && options.trailing ! == false) {timer = setTimeout(function () {// reset previous === 0 && options.leading === = false); Previous = options.leading === false? 0 : Date.now() timer = null method.apply(self, args) if (! timer) self = args = null }, remaining) } } }Copy the code

Code in some of the more important design ideas and variable use I have played on the annotation, fine down or there are a lot of subtle, such as the clever use of previous variables. The overall feeling is to see more framework source code implementation will have a lot of unexpected harvest, or to continue to refuel ah workers.

Iii. Summary at the end of the article

After in-depth understanding and implementation of the functions of anti-shake and throttling, it is not difficult to conclude some valuable conclusions, such as:

  1. Anti – tremors are the ones that persistinputIt can be continuously delayedoutputTake the search box function as an example, that is, when users continuously input content (input) causes the interface to be called (output) has been delayed untildelayThere are no new ones in the time frameinputWill execute;
  2. Throttling also addresses those that persistinputFor real-time responseoutputRequires a high level of interaction, but unlike buffeting, it does not delay the execution of the response method (output), but will guarantee more than one in a certain periodinputOnly one response is allowedoutput.

Being able to implement anti-shake and throttling functions is just a basic skill, but being able to master their implementation principles and use the right solutions for different application scenarios takes a lot of work.

4. Refer to the article

  1. Js anti-shake and throttling
  2. Talk about JS anti – shake and throttling