react example debounce javascript debouncing

example - ¿Alguien puede explicar la función "rebote" en Javascript?



lodash debounce example (5)

El código en la pregunta se alteró ligeramente del código en el enlace. En el enlace, hay una marca de (immediate && !timeout) ANTES de crear un nuevo timout. Tenerlo después provoca el modo inmediato de nunca disparar. He actualizado mi respuesta para anotar la versión de trabajo desde el enlace.

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); }; };

Estoy interesado en la función "antirrebote" en javascript, escrita aquí: http://davidwalsh.name/javascript-debounce-function

Lamentablemente, el código no se explica con la suficiente claridad como para que lo entienda. ¿Alguien puede ayudarme a descubrir cómo funciona? (Dejé mis comentarios a continuación). En resumen, realmente no entiendo cómo funciona esto

// Returns 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 // N milliseconds. function debounce(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); if (callNow) func.apply(context, args); }; };

EDITAR: El fragmento de código copiado anteriormente tenía callNow en el lugar equivocado.


Escribí una publicación titulada Demistifying Debounce en JavaScript donde explico exactamente cómo funciona una función antirrebote e incluyo una demostración.

Yo tampoco entendí completamente cómo funcionaba una función antirrebote la primera vez que encontré una. Aunque son relativamente pequeños, ¡en realidad emplean algunos conceptos de JavaScript bastante avanzados! Tener un buen control sobre el alcance, los cierres y el método setTimeout ayudará.

Dicho esto, a continuación se explica y muestra la función básica de eliminación del rebote en mi publicación mencionada anteriormente.

El producto terminado

// 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); } }; };

La explicación

// 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); } }; };


Las funciones eliminadas no se ejecutan cuando se invocan, esperan una pausa de invocaciones durante una duración configurable antes de ejecutar; cada nueva invocación reinicia el temporizador.

Las funciones de aceleración se ejecutan y luego esperan una duración configurable antes de ser elegibles para disparar nuevamente.

El rebote es excelente para los eventos de pulsación de tecla; cuando el usuario comienza a escribir y hace una pausa, envía todas las pulsaciones de tecla como un único evento, reduciendo así las invocaciones de manejo.

Throttle es ideal para puntos finales en tiempo real que solo desea permitir que el usuario invoque una vez por un período de tiempo determinado.

Echa un vistazo a Underscore.js para sus implementaciones también.


Lo importante a tener en cuenta aquí es que debounce produce una función que está "cerrada" sobre la variable de timeout . La variable de timeout permanece accesible durante cada llamada de la función producida incluso después de que se haya devuelto el debounce , y puede cambiar a través de diferentes llamadas.

La idea general para debounce es la siguiente:

  1. Comience sin tiempo de espera.
  2. Si se llama a la función producida, borre y restablezca el tiempo de espera.
  3. Si se acelera el tiempo de espera, llame a la función original.

El primer punto es simplemente var timeout; , de hecho es solo undefined . Afortunadamente, clearTimeout es bastante clearTimeout respecto a su entrada: pasar un identificador de temporizador undefined hace que simplemente no haga nada, no arroja un error o algo así.

El segundo punto lo realiza la función producida. Primero almacena cierta información sobre la llamada ( this contexto y los arguments ) en las variables para que luego pueda usarlas para la llamada rechazada. Luego borra el tiempo de espera (si hubo un conjunto) y luego crea uno nuevo para reemplazarlo usando setTimeout . Tenga en cuenta que esto sobrescribe el valor del timeout de timeout y este valor persiste en múltiples llamadas a funciones. Esto permite que el antirrebote realmente funcione: si la función se llama varias veces, el timeout se sobrescribe varias veces con un nuevo temporizador. Si este no fuera el caso, las llamadas múltiples provocarían el inicio de varios temporizadores que permanecerán activos; las llamadas simplemente se retrasarían, pero no se relanzarían.

El tercer punto se realiza en la devolución de llamada de tiempo de espera. Desconecta la variable de timeout y realiza la llamada a la función real utilizando la información almacenada de la llamada.

Se supone que el indicador immediate controla si la función debe invocarse antes o después del temporizador. Si es false , la función original no se llama hasta que se golpea el temporizador. Si es true , la función original se llama primero y no se volverá a llamar hasta que se accione el temporizador.

Sin embargo, creo que la comprobación if (immediate && !timeout) es incorrecta: el timeout acaba de establecerse en el identificador de temporizador devuelto por setTimeout por lo que !timeout setTimeout siempre es false en ese punto y por lo tanto nunca se puede llamar a la función. La versión actual de underscore.js parece tener una verificación ligeramente diferente, donde evalúa el immediate && !timeout Antes de llamar a setTimeout . (El algoritmo también es un poco diferente, por ejemplo, no utiliza clearTimeout ). Es por eso que siempre debe intentar usar la última versión de sus bibliotecas. :-)


Lo que quiere hacer es lo siguiente: Si intenta llamar a una función inmediatamente después de otra, la primera debe cancelarse y la nueva debe esperar un tiempo de espera determinado y luego ejecutarse. Entonces, en efecto, ¿necesita alguna forma de cancelar el tiempo de espera de la primera función? ¿Pero cómo? Puede llamar a la función y pasar el ID de tiempo de espera de devolución y luego pasar esa ID a cualquier función nueva. Pero la solución anterior es mucho más elegante.

Lo que hace es efectivamente hacer que la variable de timeout esté disponible en el alcance de la función devuelta. Por lo tanto, cuando se debounce() un evento de "cambio de tamaño", no vuelve a llamar a debounce() , por lo tanto, el contenido de timeout no se cambia (!) Y sigue estando disponible para la "siguiente llamada de función".

La clave aquí es básicamente que llamamos a la función interna cada vez que tenemos un evento de cambio de tamaño. Quizás esté más claro si imaginamos que todos los resize-events están en una matriz:

var events = [''resize'', ''resize'', ''resize'']; var timeout = null; for (var i = 0; i < events.length; i++){ if (immediate && !timeout) func.apply(this, arguments); clearTimeout(timeout); // does not do anything if timeout is null. timeout = setTimeout(function(){ timeout = null; if (!immediate) func.apply(this, arguments); } }

¿Ves que el timeout está disponible para la próxima iteración? Y no hay ninguna razón, en mi opinión para cambiarle el nombre a content y arguments a args .