Can someone explain the debounce function in Javascript
The code in the question was altered slightly from the code in the link. In the link, there is a check for (immediate && !timeout)
BEFORE creating a new timout. Having it after causes immediate mode to never fire. I have updated my answer to annotate the working version from the link.
function debounce(func, wait, immediate) {
// 'private' variable for instance
// The returned function will be able to reference this due to closure.
// Each call to the returned function will share this common timer.
var timeout;
// Calling debounce returns a new anonymous function
return function() {
// reference the context and args for the setTimeout function
var context = this,
args = arguments;
// Should the function be called now? If immediate is true
// and not already in a timeout then the answer is: Yes
var callNow = immediate && !timeout;
// This is the basic debounce behaviour where you can call this
// function several times, but it will only execute once
// [before or after imposing a delay].
// Each time the returned function is called, the timer starts over.
clearTimeout(timeout);
// Set the new timeout
timeout = setTimeout(function() {
// Inside the timeout function, clear the timeout variable
// which will let the next execution run when in 'immediate' mode
timeout = null;
// Check if the function already ran with the immediate flag
if (!immediate) {
// Call the original function with apply
// apply lets you define the 'this' object as well as the arguments
// (both captured before setTimeout)
func.apply(context, args);
}
}, wait);
// Immediate mode and no wait timer? Execute the function..
if (callNow) func.apply(context, args);
}
}
/////////////////////////////////
// DEMO:
function onMouseMove(e){
console.clear();
console.log(e.x, e.y);
}
// Define the debounced function
var debouncedMouseMove = debounce(onMouseMove, 50);
// Call the debounced function on every mouse move
window.addEventListener('mousemove', debouncedMouseMove);
Can someone explain the 'this' in debounce function in JavaScript?
Consider the following class:
class Foo {
constructor () {
this.a = 'a';
this.bar = debounce(this.bar, 500);
}
bar () {
console.log(this.a);
}
}
const foo = new Foo();
foo.bar();
foo.bar();
foo.bar();
So what gets logged, and when, and how many times? You are going to see one value logged, one time, about half a second after the last call. With the definition you posted you'll see a
. If you omit the contextual part you'll see undefined
:
function debounceWithoutCtx(fn, delay) {
var timer
return function (...args) {
clearTimeout(timer)
timer = setTimeout(function () {
fn(...args)
}, delay)
}
}
Understanding debounce function logic flow, particularly for Event object - where does (...args) get it's values from?
The main thing to understand with your code is that the addEventListener()
function isn't in charge of calling the debounce()
function. The debounce()
function is called when the addEventListener
gets added to the input element, not when the input event occurs. This is because calling debounce()
invokes the function, passing whatever it returns as the second argument to addEventListener()
. With that in mind, you function can be re-written as this:
const inputHandler = debounce(onInput, 500); // debounce returns a function
input.addEventListener('input', inputHandler); // the returned function is used in the addEventListener function
So the function that is returned by debounce()
is called when an input occurs (not the debounce
function itself, as this is called when the addEventListener() method is called, which is immediately when the interpreter meets this line and not when an input occurs).
Doesn't the debounce function get passed the event object in this case
and not onInput? If so, how does onInput get access to the event
object?
With the above explanation in mind, the returned function from debounce()
is what gets passed as the second argument to addEventListener()
. As a result, the returned function acts as the callback and gets passed the event object, which it has accesses to through ...args
. In the code-block above, that means inputHanlder
gets passed the event object when it gets invoked by JS when an input event occurs. So debounce()
never gets passed the event argument, it's the inner returned function that gets access to the event argument.
As the returned function (ie: the inner function in your code example), gets passed the event object, it can access it via args
. The inner function then invokes/calls the onInput
function with the event object using func.apply(null, args);
.
As for your last example, the func
function never runs as it is never called anywhere. It is passed into your function, but it never gets invoked/executed (unlike in the first example where it does get called using .apply()
). The InputEvent still gets logged though, as the addEventListener()
is what invokes the callback returned when the input occurs. As a result, the inner function still has access to the event object.
What does _.debounce do?
Basically it throttles calls so if it is called more than once in a short period of time, only one instance will be called.
Why would you use it?
Events like window.onresize fire multiple times in rapid succession. If you need to do a lot of calculations on the new position, you would not want to fire the calculations multiple times. You only want to fire it when the user has finished the resizing event.
Can you explain why debounce does this binding?
var context = this
The reason why you would put this
into a different variable in this code is simply thatfunction(){}
has a different this
value based on where it gets called. In this case it's called from setTimeout
as a callback, which means that this
would be whatever it is inside of setTimeout
instead of what it was when the debounce inner function was called
You could get around this pretty easily in modern JavaScript using arrow functions, which have a lexical this
- this
is based on where the function was created instead of where it is called.
This would be the equivalent code to the initial version with the correct this
binding.
function debounce(fn, delay) {
var timer
return function () {
var args = arguments
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
fn.apply(context, args)
function#apply
allows you to run a function with both a specific this
value applied to it as well as passing in multiple arguments in an ergonomic way. Before we had rest syntax, function#apply
was the only approach to this, now you can actually just use fn(...args)
in modern javascript (assuming you don't have to explicitly bind the this
value of the function). Just keep in mind that this
is an incredibly confusing concept for nearly everyone.
The reason why you would bind context
in general in the function as defined, is just so that debounce
is more generic and more capable of being called in different circumstances. For example, in this case, we can use this
to increment a counter based on the element that the function was called on.
In practice, you wouldn't want the same debounced function put on both, you'd want to have one function and then debounce it twice, otherwise you could end up "canceling" a click on one by clicking on the other, but it's a good example of how this
can make it more functional.
function debounce(fn, delay) {
var timer
return function() {
var args = arguments
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
const debounced = debounce(function() {
this.dataset.numClicks = (Number.parseInt(this.dataset.numClicks || 0)) + 1;
this.innerText = `Clicked ${this.dataset.numClicks} Times!`
console.log(this.innerText)
}, 500);
document.querySelectorAll('button').forEach(el => el.addEventListener('click', debounced));
div {
height: 100%;
width: 100%;
color: black;
background: pink;
}
<button>Click me!</button>
<button>Click me!</button>
debounce function not working in javascript
debounce
function returns a function which is never called when you call debounce
as
debounce(getData, 2000);
dobounce
function doesn't needs to return a function. You just need following steps to implement debounce
function:
Check if
timer
is undefined or not. If not, that means there's a timeout that we need to cancel.After that set a new timer by calling
setTimeout()
that calls the given function after specific amount of time.
Also, timer
should not be a local variable because you don't want it to reset whenever debounce
function is called.
let counter = 0;
let newValue;
let timer;
const getData = () => {
console.log("Fetching Data ..", newValue, counter++);
}
const debounce = function(fn, d) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(fn, d);
}
const betterFunction = (e) => {
newValue = e.target.value;
debounce(getData, 2000);
}
<input type="text" onkeyup="betterFunction(event)" />
Related Topics
Xml Parsing of a Variable String in JavaScript
Combination of Async Function + Await + Settimeout
Jquery .Live() VS .On() Method For Adding a Click Event After Loading Dynamic Html
Set a Default Parameter Value For a JavaScript Function
How to Iterate Over a JavaScript Object
Attach Event to Dynamic Elements in JavaScript
What Is the "Right" Json Date Format
Define a Global Variable in a JavaScript Function
How to "Properly" Create a Custom Object in JavaScript
Passing Data to a Bootstrap Modal
Chrome/Firefox Console.Log Always Appends a Line Saying 'Undefined'
Link Tag Inside Browserrouter Changes Only the Url, But Doesn't Render the Component
How to Parse a Url into Hostname and Path in JavaScript
How to Get the Difference Between Two Arrays in JavaScript
JavaScript For Detecting Browser Language Preference