You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
+7-8Lines changed: 7 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,24 +10,23 @@ importance: 5
10
10
11
11
Давайте рассмотрим реальное приложение, чтобы лучше понять это требование и выяснить, откуда оно взято.
12
12
13
-
**Например, мы хотим отслеживать движения указателя.**
13
+
**Например, мы хотим отслеживать движения мыши.**
14
14
15
15
16
16
В браузере мы можем объявить функцию, которая будет запускаться при каждом движении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, это может происходить около 100 раз в секунду (каждые 10 мс).
17
17
18
-
**Отслеживающая функция должна обновлять некоторую информацию на веб-странице.**
18
+
**Мы бы хотели обновлять информацию на странице при передвижениях.**
19
19
20
-
Функция обновления `update()` слишком ресурсоёмкая, чтобы делать это при каждом микродвижении. Также нет смысла делать это чаще, чем один раз в 100 мс.
20
+
...Но функция обновления `update()` слишком ресурсоёмкая, чтобы делать это при каждом микродвижении. Да и нет смысла делать обновление чаще, чем один раз в 100 мс.
21
21
22
-
23
-
Поэтому мы обернём вызов в декоратор: будем использовать throttle(update, 100) как функцию, которая будет запускаться при каждом перемещении указателя вместо оригинальной update(). Декоратор будет вызываться часто, но `update()` будет вызываться максимум раз в 100 мс.
22
+
Поэтому мы обернём вызов в декоратор: будем использовать `throttle(update, 100)` как функцию, которая будет запускаться при каждом перемещении указателя вместо оригинальной `update()`. Декоратор будет вызываться часто, но передавать вызов в `update()` максимум раз в 100 мс.
24
23
25
24
Визуально это будет выглядеть вот так:
26
25
27
-
1. Для первого движения указателя декорированный вариант передаёт вызов в `update`. Это важно, т.к. пользователь сразу видит нашу реакцию на его перемещение.
26
+
1. Для первого движения указателя декорированный вариант сразу передаёт вызов в `update`. Это важно, т.к. пользователь сразу видит нашу реакцию на его перемещение.
28
27
2. Затем, когда указатель продолжает движение, в течение 100 мс ничего не происходит. Декорированный вариант игнорирует вызовы.
29
-
3. По истечению 100 мс происходит ещё один вызов `update` с последними координатами.
30
-
4. Затем, наконец, указатель где-то останавливается. Декорированный вариант ждёт, пока не истечёт 100 мс и затем вызывает `update` с последними координатами. Так что, пожалуй, самое главное то, что окончательные координаты указателя обработаны.
28
+
3. По истечению 100 мс происходит ещё один вызов `update` с последними координатами.
29
+
4. Затем, наконец, указатель где-то останавливается. Декорированный вариант ждёт, пока не истечёт 100 мс и затем вызывает `update` с последними координатами. Так что, что весьма важно, окончательные координаты указателя обработаны.
1. Реализовать новую (или использовать стороннюю) структуру данных для коллекции, которая которая более универсальна чем встроенный `Map`, и поддерживает множественные ключи.
240
240
2. Использовать вложенные коллекции: `cache.set(min)` будет `Map` которая хранит пару `(max, result)`. Тогда получить `result` мы можем вызвав `cache.get(min).get(max)`.
241
-
3. Соединить два значения в одно. В нашем конкретном случае мы можем просто использовать строку `"min,max"` как ключ к `Map`. Для гибкости, мы можем позволить передавать *функцию кеширования* в декоратор, которая знает, как сделать одно значение из многих.
242
-
241
+
3. Соединить два значения в одно. В нашем конкретном случае мы можем просто использовать строку `"min,max"` как ключ к `Map`. Для гибкости, мы можем позволить передавать *хеширующую функцию* в декоратор, которая знает, как сделать одно значение из многих.
243
242
244
243
Для многих практических применений третий вариант достаточно хорош, поэтому мы будем придерживаться его.
245
244
246
-
Вторая задача, которую нужно решить, - как передать множество аргументов в `func`? Пока что обёртка `function (x)` принимает один аргумент, и `func.call (this, x)` передаёт его.
247
-
248
-
Здесь мы можем использовать другой встроенный метод [func.apply](mdn:js/Function/apply).
249
-
250
-
Синтаксис:
251
-
252
-
```js
253
-
func.apply(context, args)
254
-
```
255
-
256
-
Он выполняет `func`, устанавливая`this=context`, и принимая в качестве списка аргументов объект-псевдомассив `args`.
257
-
258
-
Например, эти два вызова почти одинаковые:
259
-
260
-
```js
261
-
func(1, 2, 3);
262
-
func.apply(context, [1, 2, 3])
263
-
```
264
-
265
-
Оба запускают `func`, передавая аргументы `1,2,3`. Но `apply` также устанавливает `this=context`.
266
-
267
-
Например, здесь `say` вызывается с `this=user` и `messageData` в качестве списка аргументов:
268
-
269
-
```js run
270
-
functionsay(time, phrase) {
271
-
alert(`[${time}] ${this.name}: ${phrase}`);
272
-
}
273
-
274
-
let user = { name:"John" };
275
-
276
-
let messageData = ['10:00', 'Hello']; // становится time и phrase
277
-
278
-
*!*
279
-
// this принимает значение user, messageData передаётся как список аргументов (time, phrase)
Единственная разница в синтаксисе между `call` и `apply` состоит в том, что `call` ожидает список аргументов, в то время как `apply` принимает псевдомассив.
285
-
Мы уже знаем оператор расширения `...` из главы <info: rest-parameters-spread-operator>, который может передавать массив (или любой перебираемый объект) в виде списка аргументов. Поэтому, если мы используем его с `call`, мы можем достичь почти того же, что и `apply`.
286
-
Эти два вызова почти эквивалентны:
287
-
288
-
```js
289
-
let args = [1, 2, 3];
290
-
291
-
*!*
292
-
func.call(context, ...args); // передаёт массив как список с оператором расширения
293
-
func.apply(context, args); // тот же эффект
294
-
*/!*
295
-
```
296
-
297
-
Если мы посмотрим более внимательно, то между такими использованиями `call` и` apply` есть небольшая разница.
298
-
299
-
- Оператор расширения `...` позволяет передавать *перебираемый* объект `args` в виде списка в `call`.
300
-
- В свою очередь `Apply` принимает только *псевдомассив*`args`.
301
-
302
-
Итак, эти вызовы дополняют друг друга. Там, где мы ожидаем итеративность, работает `call`, где мы ожидаем псевдомассив, работает `apply`.
303
-
304
-
И если `args` является перебираемый и похожим на реальный массив, то технически мы могли бы использовать любой из них, но `apply`, вероятно, будет быстрее, потому что это одна операция. Большинство движков JavaScript внутренне оптимизируют его лучше, чем пара `call + spread`.
305
-
306
-
Одним из наиболее важных применений `apply` является передача вызова другой функции, например так:
307
-
308
-
```js
309
-
letwrapper=function() {
310
-
returnanotherFunction.apply(this, arguments);
311
-
};
312
-
```
313
-
314
-
Это называется *переадресация вызова*. Обёртка передаёт все, что получает: контекст `this` и аргументы для `anotherFunction` и возвращает её результат.
245
+
Также нам понадобится заменить `func.call(this, x)` на `func.call(this, ...arguments)`, чтобы передавать все аргументы обёрнутой функции, а не только первый.
315
246
316
-
Когда внешний код вызывает такую 'обёртку', он неотличим от вызова исходной функции.
317
-
318
-
Теперь давайте заправим все это в более мощный `cachingDecorator`:
247
+
Вот более мощный `cachingDecorator`:
319
248
320
249
```js run
321
250
let worker = {
@@ -354,13 +283,52 @@ alert( worker.slow(3, 5) ); // работает
354
283
alert( "Again "+worker.slow(3, 5) ); // аналогично (из кеша)
355
284
```
356
285
357
-
Теперь обёртка оперирует любым количеством аргументов.
286
+
Теперь он работает с любым количеством аргументов.
358
287
359
288
Есть два изменения:
360
289
361
290
- В строке `(*)` вызываем `hash` для создания одного ключа из `arguments`. Здесь мы используем простую функцию "объединения", которая превращает аргументы `(3, 5)` в ключ `" 3,5 "`. В более сложных случаях могут потребоваться другие функции хеширования.
362
-
- Затем `(**)` используем `func.apply` для передачи как контекста, так и всех аргументов, полученных обёрткой (независимо от их количества), в исходную функцию.
291
+
- Затем `(**)` используем `func.call(this, ...arguments)` для передачи как контекста, так и всех аргументов, полученных обёрткой (независимо от их количества), в исходную функцию.
292
+
293
+
Вместо `func.call(this, ...arguments)` мы могли бы написать `func.apply(this, arguments)`.
294
+
295
+
Синтаксис встроенного метода [func.apply](mdn:js/Function/apply):
296
+
297
+
```js
298
+
func.apply(context, args)
299
+
```
300
+
301
+
Он выполняет `func`, устанавливая `this=context` и принимая в качестве списка аргументов псевдомассив `args`.
302
+
303
+
Единственная разница в синтаксисе между `call` и `apply` состоит в том, что `call` ожидает список аргументов, в то время как `apply` принимает псевдомассив.
304
+
305
+
Эти два вызова почти эквивалентны:
306
+
307
+
```js
308
+
func.call(context, ...args); // передаёт массив как список с оператором расширения
309
+
func.apply(context, args); // тот же эффект
310
+
```
311
+
312
+
Есть только одна небольшая разница:
313
+
314
+
- Оператор расширения `...` позволяет передавать *перебираемый* объект `args` в виде списка в `call`.
315
+
- А `apply` принимает только *псевдомассив*`args`.
316
+
317
+
Так что эти вызовы дополняют друг друга. Для перебираемых объектов сработает `call`, а где мы ожидаем псевдомассив - `apply`.
318
+
319
+
А если у нас объект, который и то и другое, например, реальный массив, то технически мы можем бы использовать любой, но `apply`, вероятно, будет быстрее, потому что большинство движков JavaScript внутренне оптимизируют его лучше.
320
+
321
+
Передача всех аргументов вместе с контекстом другой функции называется "перенаправлением вызова" (call forwarding).
322
+
323
+
Простейший вид такого перенаправления:
324
+
325
+
```js
326
+
letwrapper=function() {
327
+
returnfunc.apply(this, arguments);
328
+
};
329
+
```
363
330
331
+
При вызове `wrapper` из внешнего кода его не отличить от вызова исходной функции.
364
332
365
333
## Заимствование метода [#method-borrowing]
366
334
@@ -414,7 +382,7 @@ hash(1, 2);
414
382
415
383
Почему это работает?
416
384
417
-
Это связано с тем, что внутренний алгоритм встроенного метода arr.join(glue) очень прост.
385
+
Это связано с тем, что внутренний алгоритм встроенного метода `arr.join(glue)` очень прост.
418
386
Взято из спецификации практически "как есть":
419
387
420
388
1. Пускай первым аргументом будет `glue` или, в случае отсутствия аргументов, им будет запятая `","`
Copy file name to clipboardExpand all lines: 1-js/06-advanced-functions/10-bind/article.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,13 +5,13 @@ libs:
5
5
6
6
# Привязка контекста к функции
7
7
8
-
При использовании `setTimeout` с методами объекта (или при передаче методов объекта) возникает известная проблема: "потеря `this`".
8
+
При передаче методов объекта в качестве колбэков, например для `setTimeout`, возникает известная проблема - потеря `this`.
9
9
10
-
Внезапно, `this` просто перестает работать правильно. Такая ситуация типична для новичков, но также случается и с опытными разработчиками.
10
+
В этой главе мы посмотрим, как можно её решать.
11
11
12
12
## Потеря "this"
13
13
14
-
Мы уже знаем, что в JavaScript легко потерять `this`: когда метод передается где-то отдельно от объекта - `this` теряется.
14
+
Мы уже видели примеры потери `this`. Как только метод передается отдельно от объекта - `this` теряется.
15
15
16
16
Вот как это может произойти с `setTimeout`:
17
17
@@ -37,7 +37,7 @@ let f = user.sayHi;
37
37
setTimeout(f, 1000); // контекст user потеряли
38
38
```
39
39
40
-
Метод `setTimeout` в браузере имеет особенность: он устанавливает `this=window` для вызова функции (в Node.js `this` становится объектом таймера, но здесь это не имеет значения). Таким образом, для `this.firstName` он пытается получить `window.firstName`, которого не существует. В других подобных случаях, как мы увидим, обычно `this` просто становится `undefined`.
40
+
Метод `setTimeout` в браузере имеет особенность: он устанавливает `this=window` для вызова функции (в Node.js `this` становится объектом таймера, но здесь это не имеет значения). Таким образом, для `this.firstName` он пытается получить `window.firstName`, которого не существует. В других подобных случаях обычно `this` просто становится `undefined`.
41
41
42
42
Задача довольно типичная - мы хотим передать метод объекта куда-то ещё (в этом конкретном случае - в планировщик), где он будет вызван. Как бы сделать так, чтобы он вызывался в правильном контексте?
0 commit comments