diff --git a/js/debounce/demos/debounced-search/index.html b/js/debounce/demos/debounced-search/index.html index fbf8b7dc74..f7a68c4b7c 100644 --- a/js/debounce/demos/debounced-search/index.html +++ b/js/debounce/demos/debounced-search/index.html @@ -163,17 +163,12 @@ const searchInput = searchForm.querySelector('[type="search"]') const searchResults = document.querySelector('.search-results') - function debounce(callee, timeoutMs) { + function debounce(callback, delay) { + let timer; return function perform(...args) { - let previousCall = this.lastCall - this.lastCall = Date.now() - - if (previousCall && ((this.lastCall - previousCall) <= timeoutMs)) { - clearTimeout(this.lastCallTimer) - } - - this.lastCallTimer = setTimeout(() => callee(...args), timeoutMs) - } + clearTimeout(timer); + timer = setTimeout(() => callback.apply(this, args), delay); + }; } function handleInput(e) { diff --git a/js/debounce/index.md b/js/debounce/index.md index b30d08de87..2c85f43121 100644 --- a/js/debounce/index.md +++ b/js/debounce/index.md @@ -233,25 +233,32 @@ const server = { Итак, `debounce()` — это функция высшего порядка, принимающая аргументом функцию, которую надо «отложить». -Поехали. Аргументами будут функция, которую надо «откладывать», и интервал времени, спустя который следует вызывать функцию. Как результат возвращаем другую функцию. Это нужно, чтобы мы могли не менять другие части кода. Чуть позже увидим, как это помогает. В переменной `previousCall` мы храним временную метку предыдущего вызова, а в переменной текущего вызова — временную метку нынешнего момента. Это нужно, чтобы потом сравнить, когда функция была вызвана в этот раз и в предыдущий. Если разница между вызовами меньше, чем указанный интервал, то мы очищаем таймаут, который отвечает за непосредственно вызов функции-аргумента. Обратите внимание, что мы передаём все аргументы `...args`, которые получаем в функции `perform()`. Это тоже нужно, чтобы не приходилось менять другие части кода. Если таймаут был очищен, вызова не произойдёт. Если он не был очищен, то вызовется `callee`. Таким образом, мы как бы «отодвигаем» вызов `callee` до тех пор, пока «снаружи всё не подуспокоится». +Поехали. Аргументами будут функция, которую надо «откладывать» (`callback`), и интервал времени, спустя который следует вызывать функцию (`delay`). Как результат возвращаем другую функцию, которая является оберткой над `setTimeout` с переданной `callback` функцией. + +Обратите внимание, что мы передаём все аргументы `...args`, которые получаем в возвращаемой функции `perform`. Это нужно, чтобы передавать функцию `callback` с любыми аргументами. Если таймаут был очищен, вызова не произойдёт. Если он не был очищен, то вызовется `callback`. Таким образом, мы как бы «отодвигаем» вызов `callback` до тех пор, пока «снаружи всё не подуспокоится». ```javascript -function debounce(callee, timeoutMs) { +function debounce(callback, delay) { + let timer return function perform(...args) { - let previousCall = this.lastCall - - this.lastCall = Date.now() - - if (previousCall && this.lastCall - previousCall <= timeoutMs) { - clearTimeout(this.lastCallTimer) - } - - this.lastCallTimer = setTimeout(() => callee(...args), timeoutMs) + clearTimeout(timer) + timer = setTimeout(() => { callback.apply(this, args) }, delay) } } ``` -Использовать такой `debounce()` мы можем так: +Как работает функция `perform`: + +1. При вызове функции `perform`, сначала выполняется `clearTimeout(timer)`. Так удаляется ранее установленный таймер, если он существует. Это необходимо, чтобы предотвратить выполнение предыдущего вызова функции `callback`, если новый вызов произошёл до завершения задержки. +1. Устанавливается новый таймер с помощью `setTimeout`, и ссылка на этот таймер сохраняется в переменной `timer`. +1. В `setTimeout` первым аргументом передаётся стрелочная функция с вызовом нашего `callback` c привязкой контектса и передачей всех аргументов. Вторым аргументом передаётся задержка `delay`. + +Отметим несколько важных моментов: + +- возвращаемая функция `perform` НЕ должна быть стрелочной, чтобы не терялся контекст; +- чтобы `callback` функция, передаваемая в `setTimeout`, не потеряла контекст, его нужно привязать с помощью `apply`. Это важно при работе с обработчиком событий браузера. + +Использовать `debounce()` мы можем так: ```javascript // Функция, которую хотим «откладывать» @@ -271,7 +278,7 @@ debouncedDoSomething(42) ## Применяем `debounce()` -Теперь мы можем применить `debounce()` в нашем обработчике. Сперва немного порефакторим. Вынесем обработчик события в отдельную функцию. Внутри она будет такой же, но так нам удобнее оборачивать её в `debounce()`. +Теперь применим `debounce()` в нашем обработчике. Сперва немного порефакторим. Вынесем обработчик события в отдельную функцию. Внутри она будет такой же, но так нам удобнее оборачивать её в `debounce()`. ```javascript function handleInput(e) { @@ -291,7 +298,7 @@ function handleInput(e) { searchInput.addEventListener('input', handleInput) ``` -Теперь обернём вынесенную функцию и обновим `addEventListener`. Укажем, что нам нужно ждать 250 мс, прежде чем запустить обработчик. Дальше передаём новую `debounced`-функцию в `addEventListener`. +Обернём вынесенную функцию и обновим `addEventListener`. Укажем, что нам нужно ждать 250 мс, прежде чем запустить обработчик. Дальше передаём новую `debounced`-функцию в `addEventListener`. ```javascript function handleInput(e) { @@ -309,12 +316,10 @@ searchInput.addEventListener('input', debouncedHandle) Вместо пяти запросов теперь отправляем всего один! -Обратите внимание, что API функции не поменялось. Мы как передавали [`event`](/js/event/), так и передаём. То есть для внешнего мира debounced-функция ведёт себя точно так же, как и простая функция-обработчик. - -Это удобно, потому что меняется лишь одна небольшая часть программы, не затрагивая системы в целом. +Обратите внимание, что API функции не поменялось. Мы как передавали [`event`](/js/event/), так и передаём. То есть для внешнего мира debounced-функция ведёт себя точно так же, как и простая функция-обработчик. Это удобно, потому что меняется лишь одна небольшая часть программы, не затрагивая системы в целом. ### Результат Полный пример строки поиска у нас получится такой: - +