Series of articles:

  1. Read one NPM module (1) – username every day
  2. Read one NPM module (2) – MEM every day
  3. Read one NPM module per day (3) – Mimic-FN

The knowledge of property descriptors introduced in the previous article was too theoretical, and the throttle-debounce module read today is much more practical and can be used at work.

A one-sentence introduction

The NPM module read today is throttle-debounce, which provides functions throttle and debounce: Throttle, which means throttling, and debounce, which means anti-jitter, limit the frequency of function execution and avoid performance problems caused by multiple function executions in a short period of time. The current package version is 2.0.1 and has 63,000 downloads per week.

usage

Throttle and Debounce are first introduced, both of which can be used for function throttling to improve performance, but there are some differences:

  • Debounce: Combines events that are fired multiple times in a short period of time into one event response function execution (usually on the first event or the last event), i.e. only one actual event response function execution in that period of time.
  • Throttle: If the same event fires more than once in a short period of time, the event response function is executed at smaller intervals, that is, it may be executed more than once in that period of time.

It takes more than ten minutes to wait for the elevator.

  • Debounce: If I’m in an elevator closing the door and A wants to take the elevator, I’ll press the door button as A courtesy and wait for him to step into the elevator before trying to close the door. When A gets into the elevator, I find THAT B also wants to take the elevator, so OUT of politeness I press the open door button and wait for him to get into the elevator. If someone wanted to take the elevator all the time, I would keep pushing the close button until no one wanted to take the elevator. (If I did this in real life, I would probably do nothing but take the elevator every day.)
  • Throttle: I actually have work to do every day. I can’t wait for someone in an elevator indefinitely. So THIS time I will be capricious, I only rule that I wait 30 seconds, regardless of whether anyone wants to take the elevator then, I will press the close button and leave.

The biggest difference from the two examples above is that with throttle, whenever an event occurs (someone wants to ride the elevator), the event response will execute for a certain amount of time (I hit the close button in 30 seconds); If the Debounce method is used, it will only be executed once the event has stopped happening (I find that no one wants to ride the elevator).

You can try scrolling through the following Demo to get a sense of the difference:

See the Pen The Difference Between Throttling, Debouncing, and Neither by Elvin Peng (@elvinn) on CodePen.

For throttle-debounce, it can be used simply as follows:

import { throttle, debounce } from 'throttle-debounce';

function foo() { console.log('foo.. '); }
function bar() { console.log('bar.. '); }

const fooWrapper = throttle(200, foo);

for (let i = 1; i < 10; i++) {
  setTimeout(fooWrapper, i * 30);
}

// => foo executes three times
// => foo..
// => foo..
// => foo..

const barWrapper = debounce(200, bar);

for (let i = 1; i < 10; i++) {
  setTimeout(barWrapper, i * 30);
}

// => bar executes once
// => bar..

Copy the code

The source code to learn

Throttle implementation

After simplifying the source code, modify it as follows:

/ / source 4-1
function throttle(delay, callback) {
  let timeoutID;
  let lastExec = 0;

  function wrapper() {
    const self = this;
    const elapsed = Number(new Date()) - lastExec;
    const args = arguments;

    function exec() {
      lastExec = Number(new Date());
      callback.apply(self, args);
    }

    clearTimeout(timeoutID);

    if (elapsed > delay) {
      exec();
    } else{ timeoutID = setTimeout(exec, delay - elapsed); }}return wrapper;
}
Copy the code

The whole code logic is very clear, there are only three steps:

  1. Calculates the elapsed time since the last function executionelapsedAnd clears the timer previously set.
  2. If the elapsed time is greater than the set intervaldelayExecute the function immediately and update the last time the function was executed.
  3. If the elapsed time is smaller than the set intervaldelay, then throughsetTimeoutSet a counter so that the function is indelay - elapsedTime to execute.

Source code 4-1 is not difficult to understand, but need to pay attention to the use of this:

function throttle(delay, callback) {
    // ...
    function wrapper() {
    	const self = this;
        const args = arguments;
        // ...
        
        function exec() {
            // ...callback.apply(self, args); }}}Copy the code

In the above code, the value of this is temporarily saved by the self variable and the correct value of this is passed in the exec function via callback.apply(self, args), a common practice in closures related function calls. Because of the handling of this, we can implement the following capabilities:

function foo() { console.log(this.name);  }

const fooWithName = throttle(200, foo);

const obj = {name: 'elvin'};

fooWithName.call(obj, 'elvin');

// => 'elvin'
Copy the code

Debounce implementation

Debouncen is easier to implement because it simply delays the execution of functions and does not throttle them at regular intervals:

function debounce(delay, callback) {
  let timeoutID;

  function wrapper() {
    const self = this;
    const args = arguments;

    function exec() {
      callback.apply(self, args);
    }

    clearTimeout(timeoutID);

    timeoutID = setTimeout(exec, delay);
  }

  return wrapper;
}
Copy the code

Comparing this code to the one you are implementing with Throttle, you can see that it is the code with the elapsed logic removed, and most of the rest is exactly the same, So the debounce function can be implemented with the help of throttle-debounce (which is also done in the source code), and the throttle function can be implemented with the help of debounce.

Examples for throttle and Debounce usage scenarios

Throttle and Debounce are applicable to scenarios where the user frequently performs the same operation within a short period of time, for example:

  • Trigger when the user drags the browser window to resize the windowresizeEvents.
  • The user moves the mouse, triggeringmousemoveSuch events.
  • The user enters the input in the input box, triggeringkeydown | keypress | keyinput | keyupSuch events.
  • The user scrolls the screen, triggeringscrollEvents.
  • After the user clicks the button, the API request takes time and the response is not immediately displayed. Therefore, the user may click the button repeatedlyclickEvents.

Searching the web, I found that the two functions were sometimes used in conflicting scenarios. For example, when typing in the search box, debounce should be used to limit the flow to reduce server stress. Some say throttle can be used to throttle traffic to return users’ search results more quickly.

It does not seem to me that there is a case for using throttle and Debounce in one way and the other. It is often a matter of personal consideration and choice:

  • The event response function can be used when the CPU, GPU, traffic, and server usage is within the acceptable rangethrottleTraffic limiting brings better user experience.
  • This function can be used when the event response function occupies large resources, such as cpus, Gpus, traffic, and serversdebounceMore powerful current limiting, thus reducing pressure.

Write in the last

The throttle-Debounce source code is completely different from the module I saw Sindre write the other day. It has about three times the number of lines of comment and detailed comments for function arguments, which should be a good thing, but it doesn’t make reading the source any easier. It made my reading more laborious because of the following processing of the optional arguments:

/ / source 4-2

/** * * @param {Number} delay * @param {Boolean} [noTrailing] * @param {Function} callback * @param {Boolean} [debounceMode] * * @return {Function} A new, throttled, function. */
export default function ( delay, noTrailing, callback, debounceMode ) {
    // `noTrailing` defaults to falsy.
	if ( typeofnoTrailing ! = ='boolean' ) {
		debounceMode = callback;
		callback = noTrailing;
		noTrailing = undefined;
	}
    
    // ...
}
Copy the code

The noTrailing and debounceMode parameters are optional, delay and callback are mandatory, and the noTrailing parameter is placed before the mandatory parameter callback. Check the code in the function: if noTrailing is a function, this value should be callback, and then noTrailing is set to undefined by default.

There’s a better way to write it, even for ES5 compatibility. Here’s what I think is a better way to write it when ES6 syntax is available:

export default function (dalay, noTrailing, options = { callback = false, debounceMode = false, } = {}) {
    // ...
}
Copy the code

About me: graduated from huake, working in Tencent, elvin’s blog welcome to visit ^_^