Un projet sur lequel je travaille utilise _.debounce ().
La documentation Underscore JS pour debounce se lit comme suit:
debounce
_.debounce(function, wait, [immediate])
Crée et renvoie une nouvelle version anti-rebond de la fonction passée qui retardera son exécution jusqu'à ce que attende millisecondes se soit écoulée depuis la dernière invocation.
Cela suppose évidemment que quiconque veut savoir ce que fait debounce()
, sait déjà ce que signifie 'debounce'.
Que fait réellement le anti-rebond?
Fondamentalement, il limite les appels, donc s'il est appelé plus d'une fois en peu de temps, une seule instance sera appelée.
Pourquoi l'utiliseriez-vous?
Des événements comme window.onresize se déclenchent plusieurs fois en succession rapide. Si vous devez effectuer de nombreux calculs sur la nouvelle position, vous ne voudrez pas déclencher les calculs plusieurs fois. Vous ne souhaitez le déclencher que lorsque l'utilisateur a terminé l'événement de redimensionnement.
Description du code source de underscore.js:
Renvoie une fonction qui, tant qu'elle continue d'être invoquée, ne sera pas déclenchée. La fonction sera appelée après avoir cessé d'être appelée pendant N millisecondes. Si "immédiat" est passé, déclenchez la fonction sur le bord avant, au lieu de la fin.
Codez-le vous-même:
_.debounce = function(func, wait, immediate) {
var timeout, result;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) result = func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(context, args);
return result;
};
};
J'ai écrit un article intitulé Démystifier Debounce en JavaScript où j'explique exactement comment fonctionne une fonction anti-rebond et j'inclus une démo.
Une fonction anti-rebond permet de "limiter" l'exécution d'une fonction. Ils sont généralement utilisés dans des circonstances où une fonction est liée à un événement qui se déclenche en succession rapide. Il est courant de voir une fonction anti-rebond utilisée lors du redimensionnement et du défilement des fenêtres.
Qu'elles soient soulignées ou une autre bibliothèque JavaScript, toutes les fonctions anti-rebond reposent sur la méthode native setTimeout
de JavaScript. Donc, avant de vous plonger dans la compréhension de ce que fait une fonction anti-rebond, c'est une bonne idée d'avoir une compréhension de WindowTimers
(liens vers MDN) approfondie.
De plus, vous souhaiterez avoir une bonne compréhension de la portée et des fermetures. Bien que relativement petites, les fonctions anti-rebond emploient en fait des concepts JavaScript assez avancés!
Cela dit, ci-dessous est la fonction de base anti-rebond expliquée et démontrée dans mon article référencé ci-dessus.
Le produit fini
// Create JD Object
// ----------------
var JD = {};
// Debounce Method
// ---------------
JD.debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if ( !immediate ) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait || 200);
if ( callNow ) {
func.apply(context, args);
}
};
};
L'explication
// Create JD Object
// ----------------
/*
It's a good idea to attach helper methods like `debounce` to your own
custom object. That way, you don't pollute the global space by
attaching methods to the `window` object and potentially run in to
conflicts.
*/
var JD = {};
// Debounce Method
// ---------------
/*
Return a function, that, as long as it continues to be invoked, will
not be triggered. The function will be called after it stops being
called for `wait` milliseconds. If `immediate` is passed, trigger the
function on the leading Edge, instead of the trailing.
*/
JD.debounce = function(func, wait, immediate) {
/*
Declare a variable named `timeout` variable that we will later use
to store the *timeout ID returned by the `setTimeout` function.
*When setTimeout is called, it retuns a numeric ID. This unique ID
can be used in conjunction with JavaScript's `clearTimeout` method
to prevent the code passed in the first argument of the `setTimout`
function from being called. Note, this prevention will only occur
if `clearTimeout` is called before the specified number of
milliseconds passed in the second argument of setTimeout have been
met.
*/
var timeout;
/*
Return an anomymous function that has access to the `func`
argument of our `debounce` method through the process of closure.
*/
return function() {
/*
1) Assign `this` to a variable named `context` so that the
`func` argument passed to our `debounce` method can be
called in the proper context.
2) Assign all *arugments passed in the `func` argument of our
`debounce` method to a variable named `args`.
*JavaScript natively makes all arguments passed to a function
accessible inside of the function in an array-like variable
named `arguments`. Assinging `arguments` to `args` combines
all arguments passed in the `func` argument of our `debounce`
method in a single variable.
*/
var context = this, /* 1 */
args = arguments; /* 2 */
/*
Assign an anonymous function to a variable named `later`.
This function will be passed in the first argument of the
`setTimeout` function below.
*/
var later = function() {
/*
When the `later` function is called, remove the numeric ID
that was assigned to it by the `setTimeout` function.
Note, by the time the `later` function is called, the
`setTimeout` function will have returned a numeric ID to
the `timeout` variable. That numeric ID is removed by
assiging `null` to `timeout`.
*/
timeout = null;
/*
If the boolean value passed in the `immediate` argument
of our `debouce` method is falsy, then invoke the
function passed in the `func` argument of our `debouce`
method using JavaScript's *`apply` method.
*The `apply` method allows you to call a function in an
explicit context. The first argument defines what `this`
should be. The second argument is passed as an array
containing all the arguments that should be passed to
`func` when it is called. Previously, we assigned `this`
to the `context` variable, and we assigned all arguments
passed in `func` to the `args` variable.
*/
if ( !immediate ) {
func.apply(context, args);
}
};
/*
If the value passed in the `immediate` argument of our
`debounce` method is truthy and the value assigned to `timeout`
is falsy, then assign `true` to the `callNow` variable.
Otherwise, assign `false` to the `callNow` variable.
*/
var callNow = immediate && !timeout;
/*
As long as the event that our `debounce` method is bound to is
still firing within the `wait` period, remove the numerical ID
(returned to the `timeout` vaiable by `setTimeout`) from
JavaScript's execution queue. This prevents the function passed
in the `setTimeout` function from being invoked.
Remember, the `debounce` method is intended for use on events
that rapidly fire, ie: a window resize or scroll. The *first*
time the event fires, the `timeout` variable has been declared,
but no value has been assigned to it - it is `undefined`.
Therefore, nothing is removed from JavaScript's execution queue
because nothing has been placed in the queue - there is nothing
to clear.
Below, the `timeout` variable is assigned the numerical ID
returned by the `setTimeout` function. So long as *subsequent*
events are fired before the `wait` is met, `timeout` will be
cleared, resulting in the function passed in the `setTimeout`
function being removed from the execution queue. As soon as the
`wait` is met, the function passed in the `setTimeout` function
will execute.
*/
clearTimeout(timeout);
/*
Assign a `setTimout` function to the `timeout` variable we
previously declared. Pass the function assigned to the `later`
variable to the `setTimeout` function, along with the numerical
value assigned to the `wait` argument in our `debounce` method.
If no value is passed to the `wait` argument in our `debounce`
method, pass a value of 200 milliseconds to the `setTimeout`
function.
*/
timeout = setTimeout(later, wait || 200);
/*
Typically, you want the function passed in the `func` argument
of our `debounce` method to execute once *after* the `wait`
period has been met for the event that our `debounce` method is
bound to (the trailing side). However, if you want the function
to execute once *before* the event has finished (on the leading
side), you can pass `true` in the `immediate` argument of our
`debounce` method.
If `true` is passed in the `immediate` argument of our
`debounce` method, the value assigned to the `callNow` variable
declared above will be `true` only after the *first* time the
event that our `debounce` method is bound to has fired.
After the first time the event is fired, the `timeout` variable
will contain a falsey value. Therfore, the result of the
expression that gets assigned to the `callNow` variable is
`true` and the function passed in the `func` argument of our
`debounce` method is exected in the line of code below.
Every subsequent time the event that our `debounce` method is
bound to fires within the `wait` period, the `timeout` variable
holds the numerical ID returned from the `setTimout` function
assigned to it when the previous event was fired, and the
`debounce` method was executed.
This means that for all subsequent events within the `wait`
period, the `timeout` variable holds a truthy value, and the
result of the expression that gets assigned to the `callNow`
variable is `false`. Therefore, the function passed in the
`func` argument of our `debounce` method will not be executed.
Lastly, when the `wait` period is met and the `later` function
that is passed in the `setTimeout` function executes, the
result is that it just assigns `null` to the `timeout`
variable. The `func` argument passed in our `debounce` method
will not be executed because the `if` condition inside the
`later` function fails.
*/
if ( callNow ) {
func.apply(context, args);
}
};
};
Il retient l'exécution d'une fonction jusqu'à l'expiration d'un délai. Cela permet d'éviter l'exécution continue de fonctions lorsqu'elles ne sont pas nécessaires. Attention, puisque underscore.debounce () s'appuie sur du code complexe. La plupart du temps, une simple instruction "if" à l'intérieur de la fonction est beaucoup plus rapide que le rebounce. Vous pouvez implémenter un compteur, exécutant la méthode uniquement chaque N d'itérations, ou un délai d'attente, en vérifiant qu'au moins une certaine quantité de millisecondes s'est écoulée.