Implementation of thresholde and threshed functions

Implement a debounce function

Let's start with a section of function comments in underscore

When a sequence of calls of the returned function ends, the argument
function is triggered. The end of a sequence is defined by the wait
parameter. If immediate is passed, the argument function will be
triggered at the beginning of the sequence instead of at the end.

As you can see, debounce is a high-order function, and the returned function is used as an event callback. If repeated calls are made within a given timeout, the timing will be re timed.

Use occasion:

  1. The input box listens for the last submission event
  2. resize, mousemove, scroll and other final refresh and redraw
  3. Other occasions where multiple events need to be merged and delayed triggered into one, without tracking the intermediate state

Initial version

According to the above description, you can know that the timing needs to be restarted every time you call, so you get the first initial version.

function debounce(func, wait) {
    let timeout;
    let debounced = () => {
        if (timeout) clearTimeout(timeout);
        timeout = setTimeout(function() {
            func();
            timeout = null;
        }, wait);
    }
    return debounced;
}

Since the returned debounced function has execution context, for example, the following code:

var debounce_event_func = debounce(myMouseOver, 1000, true);
document.querySelector("#app").addEventListener("mousemove", throttle_event_func);

Add execution context

throttle_event_func not only needs to accept the event function, but also needs to point this to the Dom element. Here is the app element, so you need to add the parameter to save the context at the beginning of debounced.

function debounce(func, wait) {
    let timeout;
    let debounced = () => {
        let context = this;  // Save Dom reference
        let args = arguments;  // Save parameter [event,...]
        if (timeout) clearTimeout(timeout);
        timeout = setTimeout(function() {
            func.apply(context, args);  // apply is used here
            timeout = null;
        }, wait);
    }
    return debounced;
}

Execute immediately

In the implementation of underscore, there is also the parameter immediate. We modify the function definition according to its semantics

function debounce(func, wait, immediate) {
    let timeout;
    let debounced = () => {
        let context = this; 
        let args = arguments; 
        if (timeout) clearTimeout(timeout);
        // Extract the public part
        let lazy = () => { 
            result = func.apply(context, args);
            timeout = null;
        };
        if (immediate) {
            let callNow = !timeout;
            timeout = setTimeout(lazy, wait);
            if (callNow) func.apply(context, args);
        } else {
            timeout = setTimeout(lazy, wait);
        }
    }
    return debounced;
}

Final optimization

Finally, we return the execution result of the function (although it may not be useful, it should return a value), and add the cancel function to cancel the operation.
So we get the final debounced function.

function debounce(func, wait, immediate) {
    let timeout, result;
    let debounced = () => {
        let context = this;
        let args = arguments;
        let lazy = () => {
            result = func.apply(context, args);
            timeout = null;
        };
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // If timeout= Null, indicating the status of Midway cancellation
            let callNow = !timeout;
            timeout = setTimeout(lazy, wait);
            if (callNow) func.apply(context, args);
        } else {
            timeout = setTimeout(lazy, wait);
        }
        return result;
    }
    debounced.cancel = () => {
        clearTimeout(timeout);
        timeout = null;
    }
    return debounced;
}
window.debounce = debounce;

Implement a throttle function

Let's start with a section of function comments in underscore

Returns a function, that, when invoked, will only be triggered at most once
during a given window of time. Normally, the throttled function will run
as much as it can, without ever going more than once per wait duration;
but if you'd like to disable the execution on the leading edge, pass
{leading: false}. To disable execution on the trailing edge, ditto.

It looks a little similar to the debounce above. It also limits the number of high-frequency operations, but it does not merge the operations of multiple overlapping time windows into one, but executes at most one operation in the time window.

Use occasion

  1. resize, scroll, mousemove, etc
  2. When the state needs to be tracked at low frequency, the intermediate state needs to be tracked

Initial version

First, by definition, you can use a timestamp to complete the version of the. When the function is triggered, it checks whether the current timestamp minus the saved timestamp exceeds the window.

function throttle(func, wait) {
    let old = 0;
    let throttled = () => {
        let context = this;
        let args = arguments;
        let now = new Date().valueOf();
        if (now - old > wait) {
            func.apply(context, args);
            old = now;
        } 
    }
    return throttled;
}

Event trigger at the end of the window

The above implementation can complete the functions of leading: true and trailing: false, but if you want to realize trailing: true, you need to use a timer

function throttle(func, wait) {
    let timeout;

    let throttled = () => {
        let context = this;
        let args = arguments;
        if (!timeout) {
            timeout = setTimeout(function () {
                func.apply(context, args);
                timeout = null;
            }, wait);
        }
    }
    return throttled;
}

Combining the scheme of timestamp and timer

The above implementation is the function of leading: false and trailing: true. You only need to use these two methods together. At the same time, you should ensure that the timer is cancelled after the timestamp is triggered. Or cancel the time stamp after the timer is triggered. Then add the options parameter to the function parameter.

function throttle(func, wait, options) {
    let old = 0;
    let timeout, result;
    if (!options) { options = {}; }

    let throttled = () => {
        let context = this;
        let args = arguments;
        let now = new Date().valueOf();
        if (options.leading === false) {
            old = now;
        }
        if (now - old > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            func.apply(context, args);
            old = now;
            //////
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailling === true) {
            timeout = setTimeout(function () {
                old = new Date().valueOf();
                func.apply(context, args);
                timeout = null;
            }, wait);
        }
    }
    throttled.cancel = () => {
        clearTimeout(timeout);
        timeout = null;
        context = args = null;
    };
    return throttled;
}

Final version

After adding the cancel function, the final implementation is obtained.

function throttle(func, wait, options) {
    let old = 0;
    let timeout, result;
    if (!options) { options = {}; }

    let throttled = () => {
        let context = this;
        let args = arguments;
        let now = new Date().valueOf();
        if (options.leading === false) {
            old = now;
        }
        if (now - old > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            func.apply(context, args);
            old = now;
            //////
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailling === true) {
            timeout = setTimeout(function () {
                old = new Date().valueOf();
                func.apply(context, args);
                timeout = null;
            }, wait);
        }
    }
    throttled.cancel = () => {
        clearTimeout(timeout);
        timeout = null;
        context = args = null;
    };
    return throttled;
}

Posted by chris1 on Mon, 02 May 2022 03:23:27 +0300