Skip to content

Commit 93cfb60

Browse files
authored
Merge pull request #1903 from Sm1t/master
Обновление задач debounce, throttle в соответствии с en версией
2 parents 118f9e9 + ccf8ef4 commit 93cfb60

File tree

8 files changed

+111
-71
lines changed

8 files changed

+111
-71
lines changed
Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
function debounce(f, ms) {
2-
3-
let isCooldown = false;
4-
1+
function debounce(func, ms) {
2+
let timeout;
53
return function() {
6-
if (isCooldown) return;
7-
8-
f.apply(this, arguments);
9-
10-
isCooldown = true;
11-
12-
setTimeout(() => isCooldown = false, ms);
4+
clearTimeout(timeout);
5+
timeout = setTimeout(() => func.apply(this, arguments), ms);
136
};
14-
157
}
Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,47 @@
1-
describe("debounce", function() {
2-
before(function() {
1+
describe('debounce', function () {
2+
before(function () {
33
this.clock = sinon.useFakeTimers();
44
});
55

6-
after(function() {
6+
after(function () {
77
this.clock.restore();
88
});
99

10-
it("вызывает функцию один раз в 'ms' мс", function() {
11-
let log = '';
10+
it('для одного вызова - запускается через "ms" миллисекунд', function () {
11+
const f = sinon.spy();
12+
const debounced = debounce(f, 1000);
1213

13-
function f(a) {
14-
log += a;
15-
}
14+
debounced('test');
15+
assert(f.notCalled, 'не вызывается сразу');
16+
this.clock.tick(1000);
17+
assert(f.calledOnceWith('test'), 'вызывается после 1000ms');
18+
});
1619

17-
f = debounce(f, 1000);
20+
it('для 3 вызовов - вызывает последний через "ms" миллисекунд', function () {
21+
const f = sinon.spy();
22+
const debounced = debounce(f, 1000);
1823

19-
f(1); // вызвана
20-
f(2); // проигнорирована
24+
debounced('a');
25+
setTimeout(() => debounced('b'), 200); // проигнорирована
26+
setTimeout(() => debounced('c'), 500); // вызвана
27+
this.clock.tick(1000);
2128

22-
setTimeout(() => f(3), 100); // проигнорирована (слишком рано)
23-
setTimeout(() => f(4), 1100); // вызвана (1000 мс истекли)
24-
setTimeout(() => f(5), 1500); // проигнорирована (менее 1000 мс с последнего вызова)
29+
assert(f.notCalled, 'не вызывается после 1000ms');
2530

26-
this.clock.tick(5000);
27-
assert.equal(log, "14");
31+
this.clock.tick(500);
32+
33+
assert(f.calledOnceWith('c'), 'вызывается после 1500ms');
2834
});
2935

30-
it("сохраняет контекст вызова", function() {
36+
it('сохраняет контекст вызова', function () {
3137
let obj = {
3238
f() {
3339
assert.equal(this, obj);
34-
}
40+
},
3541
};
3642

3743
obj.f = debounce(obj.f, 1000);
38-
obj.f("test");
44+
obj.f('test');
45+
this.clock.tick(5000);
3946
});
40-
41-
});
47+
});
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!doctype html>
2+
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
3+
4+
Функция <code>handler</code> вызывается на этом поле для ввода:
5+
<br>
6+
<input id="input1" placeholder="введите текст">
7+
8+
<p>
9+
10+
А на этом поле функция <code>handler</code> вызывается с применением debounce – <code>debounce(handler, 1000)</code>:
11+
<br>
12+
<input id="input2" placeholder="введите текст">
13+
14+
<p>
15+
<button id="result">Функция <code>handler</code> помещает результат сюда</button>
16+
17+
<script>
18+
function handler(event) {
19+
result.innerHTML = event.target.value;
20+
}
21+
22+
input1.oninput = handler;
23+
input2.oninput = _.debounce(handler, 1000);
24+
</script>
Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,12 @@
11
```js demo
2-
function debounce(f, ms) {
3-
4-
let isCooldown = false;
5-
2+
function debounce(func, ms) {
3+
let timeout;
64
return function() {
7-
if (isCooldown) return;
8-
9-
f.apply(this, arguments);
10-
11-
isCooldown = true;
12-
13-
setTimeout(() => isCooldown = false, ms);
5+
clearTimeout(timeout);
6+
timeout = setTimeout(() => func.apply(this, arguments), ms);
147
};
15-
168
}
17-
```
18-
19-
Вызов `debounce` возвращает обёртку. Возможны два состояния:
20-
- `isCooldown = false` -- готова к выполнению.
21-
- `isCooldown = true` -- ожидание окончания тайм-аута.
229

23-
В первом вызове `isCoolDown = false`, поэтому вызов продолжается, и состояние изменяется на `true`.
24-
25-
Пока `isCoolDown` имеет значение `true`, все остальные вызовы игнорируются.
10+
```
2611

27-
Затем `setTimeout` устанавливает его в `false` после заданной задержки.
12+
Вызов `debounce` возвращает обёртку. При вызове он планирует вызов исходной функции через указанное количество `ms` и отменяет предыдущий такой тайм-аут.

1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,49 @@ importance: 5
44

55
# Декоратор debounce
66

7-
Результатом декоратора `debounce(f, ms)` должна быть обёртка, которая передаёт вызов `f` не более одного раза в `ms` миллисекунд.
8-
Другими словами, когда мы вызываем `debounce`, это гарантирует, что все остальные вызовы будут игнорироваться в течение `ms`.
7+
Результат декоратора `debounce(f, ms)` – это обёртка, которая откладывает вызовы `f`, пока не пройдёт `ms` миллисекунд бездействия (без вызовов, «cooldown period»), а затем вызывает `f` один раз с последними аргументами.
98

10-
Например:
9+
Другими словами, `debounce` – это так называемый секретарь, который принимает «телефонные звонки», и ждёт, пока не пройдет `ms` миллисекунд тишины. И только после этого передает «начальнику» информацию о последнем звонке (вызывает непосредственно `f`).
10+
11+
Например, у нас была функция `f` и мы заменили её на `f = debounce(f, 1000)`.
12+
13+
Затем, если обёрнутая функция вызывается в 0, 200 и 500 мс, а потом вызовов нет, то фактическая `f` будет вызвана только один раз, в 1500 мс. То есть: по истечению 1000 мс от последнего вызова.
14+
15+
![](debounce.svg)
16+
17+
...И она получит аргументы самого последнего вызова, остальные вызовы игнорируются.
18+
19+
Ниже код этого примера (используется декоратор debounce из библиотеки [Lodash](https://lodash.com/docs/4.17.15#debounce)):
1120

1221
```js no-beautify
13-
let f = debounce(alert, 1000);
22+
let f = _.debounce(alert, 1000);
23+
24+
f("a");
25+
setTimeout( () => f("b"), 200);
26+
setTimeout( () => f("c"), 500);
27+
28+
// Обёрнутая в debounce функция ждёт 1000 мс после последнего вызова, а затем запускает: alert("c")
29+
```
1430

15-
f(1); // выполняется немедленно
16-
f(2); // проигнорирован
31+
Теперь практический пример. Предположим, пользователь набирает какой-то текст, и мы хотим отправить запрос на сервер, когда ввод этого текста будет завершён.
1732

18-
setTimeout( () => f(3), 100); // проигнорирован (прошло только 100 мс)
19-
setTimeout( () => f(4), 1100); // выполняется
20-
setTimeout( () => f(5), 1500); // проигнорирован (прошло только 400 мс от последнего вызова)
33+
Нет смысла отправлять запрос для каждого набранного символа. Вместо этого мы хотели бы подождать, а затем обработать весь результат.
34+
35+
В браузере мы можем настроить обработчик событий – функцию, которая вызывается при каждом изменении поля для ввода. Обычно обработчик событий вызывается очень часто, для каждого набранного символа. Но если мы воспользуемся `debounce` на 1000мс, то он будет вызван только один раз, через 1000мс после последнего ввода символа.
36+
37+
```online
38+
39+
В этом живом примере обработчик помещает результат в поле ниже, попробуйте:
40+
41+
[iframe border=1 src="debounce" height=200]
42+
43+
Видите? На втором поле вызывается функция, обёрнутая в `debounce`, поэтому его содержимое обрабатывается через 1000мс с момента последнего ввода.
2144
```
2245

23-
На практике `debounce` полезен для функций, которые получают/обновляют данные, и мы знаем, что повторный вызов в течение короткого промежутка времени не даст ничего нового. Так что лучше не тратить на него ресурсы.
46+
Таким образом, `debounce` – это отличный способ обработать последовательность событий: будь то последовательность нажатий клавиш, движений мыши или ещё что-либо.
47+
48+
Он ждёт заданное время после последнего вызова, а затем запускает свою функцию, которая может обработать результат.
49+
50+
Задача — реализовать декоратор `debounce`.
51+
52+
Подсказка: это всего лишь несколько строк, если вдуматься :)

1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ function throttle(func, ms) {
3434

3535
1. Во время первого вызова обёртка просто вызывает `func` и устанавливает состояние задержки (`isThrottled = true`).
3636
2. В этом состоянии все вызовы запоминаются в `saveArgs / saveThis`. Обратите внимание, что контекст и аргументы одинаково важны и должны быть запомнены. Они нам нужны для того, чтобы воспроизвести вызов позднее.
37-
3. ... Затем по прошествии `ms` миллисекунд срабатывает `setTimeout`. Состояние задержки сбрасывается (`isThrottled = false`). И если мы проигнорировали вызовы, то "обёртка" выполняется с последними запомненными аргументами и контекстом.
37+
3. Затем по прошествии `ms` миллисекунд срабатывает `setTimeout`. Состояние задержки сбрасывается (`isThrottled = false`). И если мы проигнорировали вызовы, то "обёртка" выполняется с последними запомненными аргументами и контекстом.
3838

3939
На третьем шаге выполняется не `func`, а `wrapper`, потому что нам нужно не только выполнить `func`, но и ещё раз установить состояние задержки и таймаут для его сброса.

1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@ importance: 5
44

55
# Тормозящий (throttling) декоратор
66

7-
Создайте "тормозящий" декоратор `throttle(f, ms)`, который возвращает обёртку, передавая вызов в `f` не более одного раза в `ms` миллисекунд. Те вызовы, которые попадают в период "торможения", игнорируются.
7+
Создайте «тормозящий» декоратор `throttle(f, ms)`, который возвращает обёртку.
88

9-
**Отличие от `debounce` - если проигнорированный вызов является последним во время "задержки", то он выполняется в конце.**
9+
При многократном вызове он передает вызов `f` не чаще одного раза в `ms` миллисекунд.
1010

11-
Давайте рассмотрим реальное применение, чтобы лучше понять это требование и выяснить, откуда оно взято.
11+
По сравнению с декоратором `debounce` поведение совершенно другое:
12+
- `debounce` запускает функцию один раз после периода «бездействия». Подходит для обработки конечного результата.
13+
- `throttle` запускает функцию не чаще, чем указанное время `ms`. Подходит для регулярных обновлений, которые не должны быть слишком частыми.
1214

13-
**Например, мы хотим отслеживать движения мыши.**
15+
Другими словами, `throttle` похож на секретаря, который принимает телефонные звонки, но при этом беспокоит начальника (вызывает непосредственно `f`) не чаще, чем один раз в `ms` миллисекунд.
1416

17+
Давайте рассмотрим реальное применение, чтобы лучше понять это требование и выяснить, откуда оно взято.
1518

16-
В браузере мы можем объявить функцию, которая будет запускаться при каждом движении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, это может происходить около 100 раз в секунду (каждые 10 мс).
19+
**Например, мы хотим отслеживать движения мыши.**
1720

18-
**Мы бы хотели обновлять информацию на странице при передвижениях.**
21+
В браузере мы можем реализовать функцию, которая будет запускаться при каждом перемещении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, что-то около 100 раз в секунду (каждые 10 мс). **Мы бы хотели обновлять некоторую информацию на странице при передвижении указателя.**
1922

2023
...Но функция обновления `update()` слишком ресурсоёмкая, чтобы делать это при каждом микродвижении. Да и нет смысла делать обновление чаще, чем один раз в 1000 мс.
2124

0 commit comments

Comments
 (0)