Throttling and Debouncing is involved where high-frequency events are triggered, such as resize, scroll, input, etc. The core idea of debounce and throttle is to add a control layer between events and functions to delay execution. The goal is to prevent frequent execution of certain operations within a certain period of time, which would lead to a waste of resources.
The control layer between events and functions is usually implemented in two ways:
Using a timer. Each time an event is triggered, it is determined whether there is already a timer.
Using timestamp difference. Record the timestamp of the last event trigger, each time an event is triggered, determine whether the difference (delay - (now - previous)) between the current timestamp and the last execution timestamp has reached the set delay time.
Debouncing
Definition
Debouncing is the process of executing a callback function after an event is triggered for a specified time. If the event is triggered again within the specified time, the timer is reset according to the last trigger.
When an event is continuously triggered, the event handling function will only execute once if no event is triggered within a certain period. If an event is triggered again before the set time arrives, the delay is restarted. As shown in the following figure, the handle function is not executed when the scroll event is continuously triggered. Only when no scroll event is triggered within 1000 milliseconds, the scroll event will be delayed and triggered.
Example - Autocomplete Input
Unoptimized Implementation
Search box suggestion prompts: When we input character, we may request data from an API. If there is no optimization, the API will be continuously triggered from the start of the input, which will inevitably lead to resource wastage. It's also not wise to frequently change the DOM element in html tree.
// Bad code
<html>
<body>
<div> search: <input id="search" type="text"> </div>
<script>
const searchInput = document.getElementById("search");
searchInput.addEventListener('input', ajax);
function ajax(e) {
// mock
console.log(`Search data: ${e.target.value}`);
}
</script>
</body>
</html>
In the above code, we don't have any optimization. We use the ajax() method to simulate data requests. Let's see the result.
If a real API is being called, it would continuously hit the server-side interface from the moment of input. It could also easily trigger rate limiting measures of the API. For example, the API provided by Github has a maximum request limit per hour.
Debounce Optimization
The principle is to use a marker to determine whether there are multiple calls within a specified time. When there are multiple calls, the previous timer is cleared and the timing starts again. If there is no further call within the specified time, the passed-in callback function (fn) is executed.
function debounce(fn, ms) {
let timerId;
return (...args) => {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn(...args);
}, ms);
}
}
Usage
const handleSearchRequest = debounce(ajax, 500)
searchInput.addEventListener('input', handleSearchRequest);
This is much better. When users input stops, it takes the last input text as the data param to request the API, avoiding request too many times.
Throttling
Throttling is the execution of a callback function at a specified interval after an event is triggered.
When an event is continuously triggered, it ensures that the event handling function is called only once within a certain time period. A common analogy for throttling is like water flowing from a faucet. When the valve is opened, water flows down rapidly. In the spirit of frugality, we want to reduce the flow from the faucet, ideally letting it drip down at a certain interval according to our wishes. For example, when a scroll event is continuously triggered, the handle function is not executed immediately, but rather every 1000 milliseconds.
Example - Scroll to The Top
The page has many list items, and after scrolling down, we hope a 'Top' button appears that can take us back to the top when clicked. At this point, we need to get the distance from the scroll position to the top to determine whether to display the 'Top' button.
Unoptimized Implementation
<body>
<div id="container"></div>
<script>
const container = document.getElementById('container');
window.addEventListener('scroll', handleScrollTop);
function handleScrollTop() {
console.log('scrollTop: ', document.body.scrollTop);
if (document.body.scrollTop > 400) {
// Handler to show top button
} else {
// Handler to hide top button
}
}
</script>
</body>
As we can see, without any optimization, a single scroll might trigger hundreds of times, and processing each one is evidently a waste of performance.
Throttling Optimization
Implement a simple throttle function, is very similar to debounce. The difference is that here we use a flag to determine whether it has been triggered. Once it has been triggered, incoming requests are directly terminated until the previous specified interval has elapsed and the callback function has been executed, then it accepts the next process.
function throttle(fn, ms) {
let flag = false;
return (...args) => {
if (flag) return;
flag = true;
setTimeout(() => {
fn(...args)
flag = false;
}, ms);
}
}
Usage
const handleScrollTop = throttle(() => {
console.log('scrollTop: ', document.body.scrollTop);
// todo:
}, 500);
window.addEventListener('scroll', handleScrollTop);
This one is much better with the above one.
Two Ways to Implement Throttling: Timestamps and Timers
- Timestamp
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
- Timers
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
Summary
Function Debounce: Combines multiple operations into a single operation. The principle is to maintain a timer, which triggers the function after a delay time. However, if it is triggered again within the delay time, the previous timer will be cancelled and reset. In this way, only the last operation can be triggered.
Window Resize Event, It is used when window size adaptation is needed. In this case, debounce is generally used because only the last change needs to be considered.
Search Input Event, If the input interval is greater than a certain value (such as 500ms), it is considered that the user has finished inputting and then starts searching.
Function Throttle: Ensures that a function is only triggered once within a certain period. The principle is to trigger the function by determining whether a certain time has been reached.
- Scroll to Top
- Search Input Event, for example, to implement real-time search input, throttle can be used.
Difference: Function throttling ensures that no matter how frequently the event is triggered, the actual event handling function will be executed once within a specified time, while function debounce only triggers a function after the last event. For example, in the scenario of infinite page scroll loading, we need the user to send an Ajax request every once in a while when scrolling the page, instead of requesting data only when the user stops scrolling. This scenario is suitable for the use of throttling technology.
Top comments (1)
Very informative, Mark! Nicely done!