Lately I’ve been asked a lot about how I would implement a debounce function as an exercise, and I wondered why this question has become prevalent in the front-end engineering world.
The more I was asked this question, the more I thought about why it was asked, and the reasoning I came up with makes sense:
- It tests your deeper knowledge of JavaScript
- There is a practical, real-world application
- These are commonly used in modern front-end development
When looking around, there wasn’t a wealth of information on the inner workings of a debounce function and that was surprising — I know that Underscore has implementations for both debounce and throttle, but I do believe that it’s important to understand on a deeper level what they are doing before using them extensively. This blog’s purpose is to explain the nuances of JavaScript inside this (albeit, simple) implementation. There are a lot of opinions on the “correct” way to implement these functions, and this blog post is not about that. So without further ado, let’s dive right in.
Purpose of Debounce
This function is built in order to limit the amount of times a function is called — scroll events, mousemove events, and keypress events are all great examples of events that we might want to capture, but can be quite taxing if we capture them every single time they fire. In order to combat this, we implement debounce and throttle functions. We won’t discuss the throttle function in this post, but a debounce function will wait until the last time the function is called and then fire after a predetermined amount of time or once the event firing becomes inactive.
Implementation
Let’s take a look at a debounce function implementation in ES6.
const debounce = (fn, time) => {
let timeout;
return function() {
const functionCall = () => fn.apply(this, arguments);
clearTimeout(timeout);
timeout = setTimeout(functionCall, time);
}
}
Let's take a look at this step by step:
- Create a wrapper function with two arguments: a callback and an integer for the timeout — this will hold the state of the timeout. Note that the wrapper function will only be called once, when the wrapper function is referenced.
- Declare the
timeout
variable, which will beundefined
until the timeout is set in the returned function. - Return a function — this will be called every time the function is called. Make sure that the function returned is not an arrow function, as you will lose context.
- Apply
this
context to callback function, and attach arguments. -
clearTimeout
if timeout exists. -
setTimeout
and pass the applied function.
This way, the clearTimeout
resets the timeout each time the function is called, and if the function is not called within the time provided, then it will finally fire the function.
Using the function would look like this:
window.addEventListener('keyup', debounce((e) => {
console.log(e);
}, 1000));
The first argument being passed is the event handler, and the second is the amount of time in milliseconds that we would consider an element “inactive” after the last event is fired.
Explanation
There are a couple parts of this function that can be used as learning points when it comes to JavaScript:
- The returned function will take the arguments that the event handler should get — even if they aren’t explicitly declared in the function declaration. Just use the arguments variable that is automatically created when inside a function.
- fn.apply is very handy, and is perfect for this situation as we won’t always know how many arguments are being provided, therefore we can send the full object through. This will also persist the context of our function.
- The functionCall variable must be declared inside the returned function so we can call it with the correct arguments.
- We must declare the timeout variable, because if we don't pass a variable into clearTimeout, then it will globally clear timeouts, and we wouldn’t want to interfere in the global scope so as to avoid unwanted side-effects.
Conclusion
This is a problem with a simple-looking solution spanning 11 lines, but it covers a lot of different concepts that can show a deeper understanding of JavaScript if done correctly, like persisting this
, returning a function, and the .apply()
method, all encapsulated inside a practical problem that can be used in the real world.
Top comments (1)
this should not work as expected!
You are every time declaring
let timeout
, and trying toclearTimeout
theundefined
. To test this, you just need to run it multiple time fastly to realize it's broken