This paper discusses function anti chattering and function throttling in front-end development, their applications, differences and simple implementation.
In front-end development, we may often need to bind some continuously triggered events to (page) tags, such as resize, scroll, input, mousemove, keyup and keydown, but sometimes we don't want to execute functions so frequently in the process of continuously triggering events.
For example, if the user has frequent resize and scroll behaviors, the page will be re rendered continuously. If there are a large number of DOM operations in the bound callback function, the page will get stuck. In this case, the common solution is to use throttling( throttle )And anti shake( debounce )To optimize high-frequency events and reduce the execution frequency of code.
If you want to compare default, function throttling and function anti shake, you can refer to Demo effect - click me To intuitively feel the difference between them.
<!-- Original: 100 times in 1 second --> <!-- Adjustment: once per second--> <!-- 10 Perform 1000 tasks per second, and perform up to 10 tasks in 10 seconds after adjustment --> <!-- Another example --> <!-- Original: 100 water from the pond in 1 second L --> <!-- Regulation: 1 second from the pond L --> <!-- 10 1000 seconds L Water can flow out for up to 10 seconds after adjustment L Water, this is the throttling operation. -->
Function throttling can be achieved by timestamp.
Next, we try to explore the details and implementation of function throttling in the form of code.
We provide a button in the page to bind a click event to the button. Normally, each time the button is clicked, the corresponding event handling function will be triggered and executed once.
/* Page label: < button > button < / button > */ let task = (e) => console.log("click button", e); let oBtn = document.querySelector("button"); oBtn.addEventListener("click", task);
If the user clicks the button several times in a short time, the event handler will be triggered many times. Function throttling specifies that a function can only be triggered once in a unit time. If the function is triggered multiple times in this unit time, only one will take effect. In other words, the function throttling is controlled within a fixed time unit, and the event task will be executed (effective) only once.
/* Event handler */ let task = (e) => console.log("click button", e); /* Function throttling */ function throttle(func, wait) { let previous = 0; return function() { context = this; args = arguments; let now = Date.now(); if (now - previous > wait) { func.apply(context, args); previous = now; } } } /* Binding event */ let oBtn = document.querySelector("button"); oBtn.addEventListener("click", throttle(task, 1000));
The simple implementation of function throttling is given above. The code encapsulates the throttle function, which receives any (event) function and interval time, and returns a new parameter in the function. The core of the throttle function is to control whether the task function should be executed by obtaining the current timestamp in the returned function and comparing it with the interval time.
After the first execution of the event handling function, when the subsequent click event is triggered, if now - previous > wait is true (the time since the last event was triggered has exceeded the specified interval), execute the task function, otherwise ignore the click event. Note func The function of the code line apply (context, args) is to bind the specific tag to this in the event handler function. In addition, there may be the transfer of parameters such as event object in the event handler function, which should be taken into account.
function throttle(func, wait, options) { let timeout,args, context, previous = 0, let throttled = function() { context = this; args = arguments; let now = Date.now(); /* The first click of this line of code setting does not take effect */ if (!previous && options.leading === false) previous = now; let remaning = wait - (now - previous); /* If: This is the first trigger event */ /* Then: execute the event handling function and update the previous value. If there is a timer, clean it up */ if (remaning <= 0) { if (timeout) { clearTimeout(timeout); timeout = null; } func.apply(context, args); previous = now; } else if (!timeout && options.trailing !== false) { /* If: the event is not triggered for the first time & & the timer is empty & & tracking = = true */ /* Then: always execute the handler triggered by the last event */ timeout = setTimeout(() =>{ previous = options.leading === false ? 0 : Date.now(); func.apply(context, args); args = context = null }, remaning); } } return throttled; } /* Any handler */ function task(e) { console.log('click', e); } oBtn.addEventListener('click', throttle(task, 1000, { leading: false,/* When set to false, the first click will not take effect */ trailing: true /* Set the last click to always trigger */ }));
Function anti shake( debounce )This means that the function can only be executed once within n seconds after the event is triggered. If the event is triggered again within n seconds, the function execution time will be recalculated from the beginning.
Function anti shake can be realized by timer.
Let's assume that the event handler function can only be executed once within 1 second when clicking the page button. If the time interval does not exceed 1 second when clicking the button next time, the timing will be restarted. Here is a simple code implementation for your reference:
/* <button></button> */ /* Task execution function */ let task = (e) => console.log("task", e); /* Anti shake function */ function debounce(func, wait) { let timer; return function() { clearTimeout(timer); /* Clean up the previous timer first (delay function) */ let context = this; let args = arguments; /* Start the timer and execute the task function task after the specified time */ timer = setTimeout(() => { func.apply(context, args); timer = null; }, wait); } } /* Get label */ let oBtn = document.querySelector("button"); /* Registration event */ oBtn.addEventListener("click", debounce(task, 1000));
Slightly adjust the above code. Suppose we want to control whether to execute the task function when the event is triggered for the first time through a parameter, you can refer to the following writing method:
/* <button></button> */ /* Task execution function */ let task = (e) => console.log("task", e); /* Anti shake function func:Specific event handling function (task function) wait:Specified time in milliseconds immediate: Boolean parameter, whether to execute once at the beginning */ function debounce(func, wait, immediate) { let timer; return function() { clearTimeout(timer); /* Clean up the previous timer (delay function) */ let context = this; let args = arguments; /* Do you want to execute once at the beginning */ if (immediate) { let callNow = !timer; if (callNow) func.apply(context, args); } /* Start the timer and execute the task function task after the specified time */ timer = setTimeout(() => { func.apply(context, args); timer = null; }, wait); } } /* Get label */ let oBtn = document.querySelector("button"); /* Registration event */ oBtn.addEventListener("click", debounce(task, 1000, true));
To sum up, both function anti chattering and function throttling prevent the frequent triggering of an event, but the principle is different: function anti chattering is executed only once in a certain period of time, while function throttling is executed at intervals.
Finally, simply post the famous frame lodash Some demonstration code about function anti shake and function throttling in, and Github is attached Open source address.
/* debounce.js File implementation */ function debounce(func, wait, opts = {}) { let maxWait; if ('maxWait' in opts) { maxWait = opts.maxWait; } let leading = true; // Triggered on first click let trailing = true; // Trigger the last time let lastCallTime; // Last call time previous let timeout; let lastThis; // Returns this of the function let lastArgs; // Returns the parameters of the function // shouldInvoke indicates whether the let lastInvokeTime; let shouldInvoke = function(now) { let sinceLastTime = now - lastCallTime; let sinceLastInvoke = now - lastInvokeTime; // for the first time return lastCallTime === undefined || sinceLastTime > wait || sinceLastInvoke >= maxWait; } // Is leadingEdge executed for the first time let invokeFunc = function(time) { // The time of the final call to the function lastInvokeTime = time; func.apply(lastThis, lastArgs); } // startTimer starts a timer let startTimer = function(timerExpired, wait) { timeout = setTimeout(timerExpired, wait); } let remainingWait = function(now) { return wait - (now - lastCallTime); } let trailingEdge = function(time) { timeout = undefined; if (trailing) { invokeFunc(time); } } let timerExpired = function() { let now = Date.now(); // The current timer is up. See if you need to execute this function if (shouldInvoke(now)) { // If it needs to be called, the end method is triggered return trailingEdge(now); } startTimer(timerExpired, remainingWait(now)); } let leadingEdge = function(time) { lastInvokeTime = time; // Call the function if it needs to be executed if (leading) { invokeFunc(time) } } // Start a timer to see if func needs to be executed when the next timer arrives startTimer(timerExpired, wait); let debounced = function(...args) { lastThis = this; lastArgs = args; let now = Date.now(); // Judge whether the current debounce needs to be executed let isInvoking = shouldInvoke(now); lastCallTime = now; if (isInvoking) { if (timeout === undefined) { leadingEdge(now); } } } return debounced; } /* throttle.js File implementation */ function throttle(func, wait) { return debounce(func, wait, { // maxWait maximum click time maxWait: wait }); }