Can Someone Explain the "Debounce" Function in JavaScript

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?

  1. var context = this

The reason why you would put this into a different variable in this code is simply that
function(){} 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)
}
}

  1. 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:

  1. Check if timer is undefined or not. If not, that means there's a timeout that we need to cancel.

  2. 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



Leave a reply



Submit