concept

Throttling function

Execute incoming methods at fixed intervals

The purpose is to prevent the function from executing too frequently and affecting performance. Common with functions tied to scrolling, mouse movement events.

The anti-shake function may be better understood by people who have come into contact with hardware. When the hardware button is pressed, the current fluctuation will be triggered many times due to the different length of time the user holds it down. Adding a anti-shake function will only trigger once to prevent the problems caused by meaningless current fluctuation.

Why should the button Debounce? Mechanical buttons do not touch well when pressed, especially mechanical switches with reed, which will open and close repeatedly at the moment of contact until the switch state is completely changed.

Application on the front end, the common scenario is that the end of the input box type action after a period of time to trigger the query/search/check, rather than to go to every play a word triggered, causing meaningless ajax queries, etc., or adjust the window size and binding function, really only need to perform an action after the last window size is fixed.

Self realization

Image stabilization function

The key point is that each trigger clears the handle of the delay function. Only the last trigger does not clear the handle, so the last trigger will wait for the default 1s to execute the parameter f passed in by debounce. The closure function returned inside debounce is the function that is actually fired each time it is called, no longer f, so arguments takes the arguments from the closure environment variable and passes it to f when f is executed, outside of the setTimeout function.

let debounce = function(f, interval = 1000) {
        let handler = null;
        return function() {
            if (handler) {
                clearTimeout(handler);
            }
            let arg = arguments;
            handler = setTimeout(function() {
                // Use appy in part to pass in the ARG argument
                // In order to change the direction of this
                // Otherwise, the function will point to the window instead of the input DOM object.
                f.apply(this, arg);
                clearTimeout(handler);
            }, interval)
        }
    }

Copy the code

Application:

 let input = document.querySelector('#input');
    input.addEventListener('input', debounce(function(e) {
        console.log("Your input is",e.target.value)
    }))
Copy the code

Let me rewrite it

function debounce(fn){
		let timer = null;
		return function(){
			clearTimeout(timer);
			let arg = arguments;
			timer = setTimeout(() = >{
				// fn.apply(this,arg);
				fn()
			},1000); }}function sayHi() {
		console.log("Test this points to".this);
    }

    var inp = document.getElementById('inp');
    inp.addEventListener('input', debounce(sayHi)); / / image stabilization
Copy the code

At this point, the console prints with input:

ƒ, Symbol(symbol.iterator): ƒ] test.html: 17 ƒ, Symbol(symbol.iterator): ƒ] test.html:12 ƒ [ƒ, callee: ƒ, Symbol(symbol.iterator): ƒ] ƒ [ƒ, Symbol(symbol.iterator): ƒ] ƒ, Symbol(symbol.iterator): ƒ] test.html:24 thissss Window {Window: Window, self: Window, document: document, name: ", "location, location,... } alert: ƒ alert () atob: ƒ atob () the blur: ƒ blur () btoa: ƒ btoa () caches:...Copy the code

If fn.apply(this,arg); , the output is:

test thisPointing to the < input id ="inp">​
Copy the code

More advanced implementations will also consider whether to use leading and trailing parameters to perform a function first and remove the following jitters, or to perform a function last and remove the preceding jitters, as in my example. The anti-shake function of Loadash will be analyzed in detail later.

Throttling function

let throttle = function(f,gap = 300){
            let lastCall = 0;
            return function(){
                let now = Date.now();
                let ellapsed = now - lastCall;
                if(ellapsed < gap){
                    return
                }
                f.apply(this.arguments);
                lastCall = Date.now(); }}Copy the code

The closure function records the time since the last call as it is being called, and returns if the interval is less than the time set by the throttling without executing the actual wrapped function f. F is called only when the interval is greater than the gap set by the throttling function, and the call time is updated.

Application:

  document.addEventListener('scroll', throttle(function (e) {
        // Determine whether to scroll to the bottom of the logic
        console.log(e,document.documentElement.scrollTop);
  }));
Copy the code

Similarly, the throttling function can be set to leading and Trailling to ensure the start or last execution.

Lodash source code analysis

The above is the throttling function of the most basic simple implementation, we next analyze the throttling function of the anti-shake analysis in the LoDash library.

Use of throttling functions

$(window).on('scroll', _.debounce(doSomething, 200));
Copy the code
function debounce(func, wait, options) {
        var lastArgs,
            lastThis,
            result,
            timerId,
            lastCallTime = 0,
            lastInvokeTime = 0,
            leading = false,
            maxWait = false,
            trailing = true;

        if (typeoffunc ! ='function') {
            throw new TypeError(FUNC_ERROR_TEXT);
        }
        wait = wait || 0;
        if(isObject(options)) { leading = !! options.leading; maxWait ='maxWait' in options && Math.max((options.maxWait) || 0, wait);
            trailing = 'trailing' inoptions ? !!!!! options.trailing : trailing; }function invokeFunc(time) {
            var args = lastArgs,
                thisArg = lastThis;

            lastArgs = lastThis = undefined;
            lastInvokeTime = time;
            result = func.apply(thisArg, args);
            return result;
        }

        function leadingEdge(time) {
            console.log("leadingEdge setTimeout")
            // Reset any `maxWait` timer.
            lastInvokeTime = time;
            // Start the timer for the trailing edge.
            timerId = setTimeout(timerExpired, wait);
            // Invoke the leading edge.
            return leading ? invokeFunc(time) : result;
        }

        function remainingWait(time) {
            var timeSinceLastCall = time - lastCallTime,
                timeSinceLastInvoke = time - lastInvokeTime,
                result = wait - timeSinceLastCall;
                console.log("remainingWait",result)
            return maxWait === false ? result : Math.min(result, maxWait - timeSinceLastInvoke);
        }

        function shouldInvoke(time) {
            console.log("shouldInvoke")
            var timeSinceLastCall = time - lastCallTime,
                timeSinceLastInvoke = time - lastInvokeTime;
            console.log("time",time,"lastCallTime",lastCallTime,"timeSinceLastCall",timeSinceLastCall)
            console.log("time",time,"lastInvokeTime",lastInvokeTime,"timeSinceLastInvoke",timeSinceLastInvoke)
            console.log("should?", (! lastCallTime || (timeSinceLastCall >= wait) || (timeSinceLastCall <0) || (maxWait ! = =false && timeSinceLastInvoke >= maxWait)))
            // 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 || (timeSinceLastCall >= wait) || (timeSinceLastCall <0) || (maxWait ! = =false && timeSinceLastInvoke >= maxWait));
        }

        function timerExpired() {
            console.log("timerExpired")
            var time = Date.now();
            if (shouldInvoke(time)) {
                return trailingEdge(time);
            }
            console.log("Restart the timer.",time,remainingWait(time))
            // Restart the timer.
            console.log("timerExpired setTimeout")
            timerId = setTimeout(timerExpired, remainingWait(time));
        }

        function trailingEdge(time) {
            clearTimeout(timerId);
            timerId = undefined;

            // Only invoke if we have `lastArgs` which means `func` has been
            // debounced at least once.
            console.log("trailing",trailing,"lastArgs",lastArgs)
            if (trailing && lastArgs) {
                return invokeFunc(time);
            }
            lastArgs = lastThis = undefined;
            return result;
        }

        function cancel() {
            if(timerId ! = =undefined) {
                clearTimeout(timerId);
            }
            lastCallTime = lastInvokeTime = 0;
            lastArgs = lastThis = timerId = undefined;
        }

        function flush() {
            return timerId === undefined ? result : trailingEdge(Date.now());
        }

        function debounced() {
            var time = Date.now(),
                isInvoking = shouldInvoke(time);
            console.log("time",time);
            console.log("isInvoking",isInvoking);
            lastArgs = arguments;
            lastThis = this;
            lastCallTime = time;

            if (isInvoking) {
                if (timerId === undefined) {
                    return leadingEdge(lastCallTime);
                }
                // Handle invocations in a tight loop.
                clearTimeout(timerId);
                console.log("setTimeout")
                timerId = setTimeout(timerExpired, wait);
                return invokeFunc(lastCallTime);
            }
            return result;
        }
        debounced.cancel = cancel;
        debounced.flush = flush;
        return debounced;
    }
Copy the code

ref

1.Debouncing and Throttling Explained Through Examples

2. Ouncing and Throttling

3. Lodash source code