diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js
index 065a77d1f9..3999156b7c 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js
@@ -1,15 +1,7 @@
-function debounce(f, ms) {
-
- let isCooldown = false;
-
+function debounce(func, ms) {
+ let timeout;
return function() {
- if (isCooldown) return;
-
- f.apply(this, arguments);
-
- isCooldown = true;
-
- setTimeout(() => isCooldown = false, ms);
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(this, arguments), ms);
};
-
}
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js
index 774bc9a6fa..bceaefee24 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js
@@ -1,41 +1,47 @@
-describe("debounce", function() {
- before(function() {
+describe('debounce', function () {
+ before(function () {
this.clock = sinon.useFakeTimers();
});
- after(function() {
+ after(function () {
this.clock.restore();
});
- it("вызывает функцию один раз в 'ms' мс", function() {
- let log = '';
+ it('для одного вызова - запускается через "ms" миллисекунд', function () {
+ const f = sinon.spy();
+ const debounced = debounce(f, 1000);
- function f(a) {
- log += a;
- }
+ debounced('test');
+ assert(f.notCalled, 'не вызывается сразу');
+ this.clock.tick(1000);
+ assert(f.calledOnceWith('test'), 'вызывается после 1000ms');
+ });
- f = debounce(f, 1000);
+ it('для 3 вызовов - вызывает последний через "ms" миллисекунд', function () {
+ const f = sinon.spy();
+ const debounced = debounce(f, 1000);
- f(1); // вызвана
- f(2); // проигнорирована
+ debounced('a');
+ setTimeout(() => debounced('b'), 200); // проигнорирована
+ setTimeout(() => debounced('c'), 500); // вызвана
+ this.clock.tick(1000);
- setTimeout(() => f(3), 100); // проигнорирована (слишком рано)
- setTimeout(() => f(4), 1100); // вызвана (1000 мс истекли)
- setTimeout(() => f(5), 1500); // проигнорирована (менее 1000 мс с последнего вызова)
+ assert(f.notCalled, 'не вызывается после 1000ms');
- this.clock.tick(5000);
- assert.equal(log, "14");
+ this.clock.tick(500);
+
+ assert(f.calledOnceWith('c'), 'вызывается после 1500ms');
});
- it("сохраняет контекст вызова", function() {
+ it('сохраняет контекст вызова', function () {
let obj = {
f() {
assert.equal(this, obj);
- }
+ },
};
obj.f = debounce(obj.f, 1000);
- obj.f("test");
+ obj.f('test');
+ this.clock.tick(5000);
});
-
-});
+});
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg
new file mode 100644
index 0000000000..8e128ce74d
--- /dev/null
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html
new file mode 100644
index 0000000000..aab1a471bb
--- /dev/null
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html
@@ -0,0 +1,24 @@
+
+
+
+Функция handler
вызывается на этом поле для ввода:
+
+
+
+
+
+А на этом поле функция handler
вызывается с применением debounce – debounce(handler, 1000)
:
+
+
+
+
+ + + \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md index 82c2ecb5fe..5952020f11 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md @@ -1,27 +1,12 @@ ```js demo -function debounce(f, ms) { - - let isCooldown = false; - +function debounce(func, ms) { + let timeout; return function() { - if (isCooldown) return; - - f.apply(this, arguments); - - isCooldown = true; - - setTimeout(() => isCooldown = false, ms); + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, arguments), ms); }; - } -``` - -Вызов `debounce` возвращает обёртку. Возможны два состояния: -- `isCooldown = false` -- готова к выполнению. -- `isCooldown = true` -- ожидание окончания тайм-аута. -В первом вызове `isCoolDown = false`, поэтому вызов продолжается, и состояние изменяется на `true`. - -Пока `isCoolDown` имеет значение `true`, все остальные вызовы игнорируются. +``` -Затем `setTimeout` устанавливает его в `false` после заданной задержки. +Вызов `debounce` возвращает обёртку. При вызове он планирует вызов исходной функции через указанное количество `ms` и отменяет предыдущий такой тайм-аут. \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md index 9c87aa129f..bea131145e 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md @@ -4,20 +4,49 @@ importance: 5 # Декоратор debounce -Результатом декоратора `debounce(f, ms)` должна быть обёртка, которая передаёт вызов `f` не более одного раза в `ms` миллисекунд. -Другими словами, когда мы вызываем `debounce`, это гарантирует, что все остальные вызовы будут игнорироваться в течение `ms`. +Результат декоратора `debounce(f, ms)` – это обёртка, которая откладывает вызовы `f`, пока не пройдёт `ms` миллисекунд бездействия (без вызовов, «cooldown period»), а затем вызывает `f` один раз с последними аргументами. -Например: +Другими словами, `debounce` – это так называемый секретарь, который принимает «телефонные звонки», и ждёт, пока не пройдет `ms` миллисекунд тишины. И только после этого передает «начальнику» информацию о последнем звонке (вызывает непосредственно `f`). + +Например, у нас была функция `f` и мы заменили её на `f = debounce(f, 1000)`. + +Затем, если обёрнутая функция вызывается в 0, 200 и 500 мс, а потом вызовов нет, то фактическая `f` будет вызвана только один раз, в 1500 мс. То есть: по истечению 1000 мс от последнего вызова. + + + +...И она получит аргументы самого последнего вызова, остальные вызовы игнорируются. + +Ниже код этого примера (используется декоратор debounce из библиотеки [Lodash](https://lodash.com/docs/4.17.15#debounce)): ```js no-beautify -let f = debounce(alert, 1000); +let f = _.debounce(alert, 1000); + +f("a"); +setTimeout( () => f("b"), 200); +setTimeout( () => f("c"), 500); + +// Обёрнутая в debounce функция ждёт 1000 мс после последнего вызова, а затем запускает: alert("c") +``` -f(1); // выполняется немедленно -f(2); // проигнорирован +Теперь практический пример. Предположим, пользователь набирает какой-то текст, и мы хотим отправить запрос на сервер, когда ввод этого текста будет завершён. -setTimeout( () => f(3), 100); // проигнорирован (прошло только 100 мс) -setTimeout( () => f(4), 1100); // выполняется -setTimeout( () => f(5), 1500); // проигнорирован (прошло только 400 мс от последнего вызова) +Нет смысла отправлять запрос для каждого набранного символа. Вместо этого мы хотели бы подождать, а затем обработать весь результат. + +В браузере мы можем настроить обработчик событий – функцию, которая вызывается при каждом изменении поля для ввода. Обычно обработчик событий вызывается очень часто, для каждого набранного символа. Но если мы воспользуемся `debounce` на 1000мс, то он будет вызван только один раз, через 1000мс после последнего ввода символа. + +```online + +В этом живом примере обработчик помещает результат в поле ниже, попробуйте: + +[iframe border=1 src="debounce" height=200] + +Видите? На втором поле вызывается функция, обёрнутая в `debounce`, поэтому его содержимое обрабатывается через 1000мс с момента последнего ввода. ``` -На практике `debounce` полезен для функций, которые получают/обновляют данные, и мы знаем, что повторный вызов в течение короткого промежутка времени не даст ничего нового. Так что лучше не тратить на него ресурсы. +Таким образом, `debounce` – это отличный способ обработать последовательность событий: будь то последовательность нажатий клавиш, движений мыши или ещё что-либо. + +Он ждёт заданное время после последнего вызова, а затем запускает свою функцию, которая может обработать результат. + +Задача — реализовать декоратор `debounce`. + +Подсказка: это всего лишь несколько строк, если вдуматься :) \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md index 4d64b9d427..1f1a8c24c1 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md @@ -34,6 +34,6 @@ function throttle(func, ms) { 1. Во время первого вызова обёртка просто вызывает `func` и устанавливает состояние задержки (`isThrottled = true`). 2. В этом состоянии все вызовы запоминаются в `saveArgs / saveThis`. Обратите внимание, что контекст и аргументы одинаково важны и должны быть запомнены. Они нам нужны для того, чтобы воспроизвести вызов позднее. -3. ... Затем по прошествии `ms` миллисекунд срабатывает `setTimeout`. Состояние задержки сбрасывается (`isThrottled = false`). И если мы проигнорировали вызовы, то "обёртка" выполняется с последними запомненными аргументами и контекстом. +3. Затем по прошествии `ms` миллисекунд срабатывает `setTimeout`. Состояние задержки сбрасывается (`isThrottled = false`). И если мы проигнорировали вызовы, то "обёртка" выполняется с последними запомненными аргументами и контекстом. На третьем шаге выполняется не `func`, а `wrapper`, потому что нам нужно не только выполнить `func`, но и ещё раз установить состояние задержки и таймаут для его сброса. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md index fe5949e7df..f6f7aab282 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md @@ -4,18 +4,21 @@ importance: 5 # Тормозящий (throttling) декоратор -Создайте "тормозящий" декоратор `throttle(f, ms)`, который возвращает обёртку, передавая вызов в `f` не более одного раза в `ms` миллисекунд. Те вызовы, которые попадают в период "торможения", игнорируются. +Создайте «тормозящий» декоратор `throttle(f, ms)`, который возвращает обёртку. -**Отличие от `debounce` - если проигнорированный вызов является последним во время "задержки", то он выполняется в конце.** +При многократном вызове он передает вызов `f` не чаще одного раза в `ms` миллисекунд. -Давайте рассмотрим реальное применение, чтобы лучше понять это требование и выяснить, откуда оно взято. +По сравнению с декоратором `debounce` поведение совершенно другое: +- `debounce` запускает функцию один раз после периода «бездействия». Подходит для обработки конечного результата. +- `throttle` запускает функцию не чаще, чем указанное время `ms`. Подходит для регулярных обновлений, которые не должны быть слишком частыми. -**Например, мы хотим отслеживать движения мыши.** +Другими словами, `throttle` похож на секретаря, который принимает телефонные звонки, но при этом беспокоит начальника (вызывает непосредственно `f`) не чаще, чем один раз в `ms` миллисекунд. +Давайте рассмотрим реальное применение, чтобы лучше понять это требование и выяснить, откуда оно взято. -В браузере мы можем объявить функцию, которая будет запускаться при каждом движении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, это может происходить около 100 раз в секунду (каждые 10 мс). +**Например, мы хотим отслеживать движения мыши.** -**Мы бы хотели обновлять информацию на странице при передвижениях.** +В браузере мы можем реализовать функцию, которая будет запускаться при каждом перемещении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, что-то около 100 раз в секунду (каждые 10 мс). **Мы бы хотели обновлять некоторую информацию на странице при передвижении указателя.** ...Но функция обновления `update()` слишком ресурсоёмкая, чтобы делать это при каждом микродвижении. Да и нет смысла делать обновление чаще, чем один раз в 1000 мс.