Может кто-нибудь объяснить функцию "debounce" в Javascript
меня интересует функция "debouncing" в javascript, написанная здесь:http://davidwalsh.name/javascript-debounce-function
к сожалению, код не объясняется достаточно ясно для меня, чтобы понять. Может ли кто-нибудь помочь мне понять, как это работает (я оставил свои комментарии ниже). Короче говоря, я просто действительно не понимаю, как это работает
// 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);
};
};
EDIT: скопированный фрагмент кода ранее имел callNow
в неположенном месте.
5 ответов:
код в вопросе был немного изменен от кода в ссылке. В ссылке, есть чек на
(immediate && !timeout)
перед созданием нового таймаута. Наличие его после причиняет немедленный режим никогда не стрелять. Я обновил свой ответ, чтобы аннотировать рабочую версию по ссылке.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); }; };
важная вещь, чтобы отметить здесь заключается в том, что
debounce
производит функции то есть "закрыто над"timeout
переменной. Элементtimeout
переменная остается доступной во время каждого вызова производимой функции даже послеdebounce
сам вернулся, а можете изменение по различным вызовам.общая идея
debounce
следующий:
- пуск без ожидания.
- если производственная функция называется, очистите и сбросьте тайм-аут.
- если время ожидания истекло, вызовите исходную функцию.
первый пункт просто
var timeout;
, это действительно простоundefined
. К счастью,clearTimeout
довольно слабо о его входе: передачаundefined
идентификатор таймера заставляет его просто ничего не делать, он не выдает ошибку или что-то еще.второй пункт сделан произведенной функцией. Он сначала хранит некоторую информацию о вызове (
this
контекст иarguments
) в переменных, поэтому он может позже использовать их для отмененного вызова. Затем он очищает тайм-аут (если был один набор), а затем создает новый, чтобы заменить его с помощьюsetTimeout
. обратите внимание, что это заменяет значениеtimeout
и это значение сохраняется в течение нескольких вызовов функций! это позволяет debounce фактически работать: если функция вызывается несколько раз,timeout
перезаписывается несколько раз, с новым таймером. Если бы это было не так, несколько вызовов вызовут несколько таймеров для запуска которых все оставайтесь активными-звонки будут просто отложены, но не отменены.третий пункт делается в тайм-аут обратного вызова. Это выбивает
timeout
переменная и выполняет фактический вызов функции, используя сохраненную информацию о вызове.The
immediate
флаг должен контролировать, следует ли вызывать функцию до или после таймер. Если этоfalse
оригинал функция не вызывается до после таймер сработал. Если этоtrue
исходная функция первый вызывается и больше не будет вызываться до тех пор, пока таймер не будет нажат.тем не менее, я считаю, что
if (immediate && !timeout)
проверить не так:timeout
только что был установлен в идентификатор таймера, возвращенныйsetTimeout
так!timeout
всегдаfalse
в этот момент и, таким образом, функция никогда не может быть вызвана. текущая версия подчеркивания.js кажется, есть немного другая проверка, где он оцениваетimmediate && !timeout
до вызовsetTimeout
. (Алгоритм также немного отличается, например, он не используетclearTimeout
.) Вот почему вы всегда должны стараться использовать последние версии библиотек. : -)
отмененные функции не выполняются при вызове, они ждут паузы вызовов в течение настраиваемой продолжительности перед выполнением; каждый новый вызов перезапускает таймер.
Дросселированные функции выполняются, а затем ждут настраиваемой продолжительности, прежде чем иметь право на повторный запуск.
Debounce отлично подходит для событий нажатия клавиш; когда пользователь начинает печатать, а затем делает паузу, вы отправляете все нажатия клавиш как одно событие, тем самым сокращая обработку вызовы.
дроссельная заслонка отлично подходит для конечных точек в реальном времени, которые вы хотите разрешить пользователю вызывать только один раз за заданный период времени.
проверить подчеркивания.js для их реализации тоже.
Я написал пост под названием демистификация Debounce в JavaScript где я точно объяснить как работает функция debounce и включить демо.
Я тоже не совсем понял, как работает функция debounce, когда я впервые столкнулся с ней. Несмотря на относительно небольшой размер, они на самом деле используют некоторые довольно продвинутые концепции JavaScript! Имея хорошее сцепление с областью, закрытие и
setTimeout
метод поможет.С этим сказал, ниже основная функция debounce объяснена и продемонстрирована в моем посте, упомянутом выше.
готовая продукция
// 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); } }; };
объяснение
// 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); } }; };
то, что вы хотите сделать, это следующее: Если вы пытаетесь вызвать функцию сразу после другого, первый должен быть отменен и новый должен ждать заданного тайм-аута, а затем выполнить. Итак, по сути вам нужен какой-то способ отмены таймаута первой функции? Но как? Ты может вызовите функцию и передайте возвращаемый идентификатор timeout-id, а затем передайте этот идентификатор в любые новые функции. Но решение выше является более элегантным.
что это делает это эффективно сделать
timeout
переменная, доступная в области возвращаемой функции. Поэтому при запуске события "resize" он не вызываетdebounce()
опять же, отсюдаtimeout
содержание не изменяется (!) и по-прежнему доступны для "следующего вызова функции".ключевым моментом здесь является то, что мы вызываем внутреннюю функцию каждый раз, когда у нас есть событие изменения размера. Возможно, это более понятно, если мы представим, что все resize-события находятся в массиве:
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); } }
видишь
timeout
доступна ли следующая итерация? И нет никакого резона, на мой взгляд переименовыватьthis
доcontent
иarguments
доargs
.