diff --git a/1-js/6-objects-more/7-bind/article.md b/1-js/6-objects-more/7-bind/article.md index c21511fa..4c283f65 100644 --- a/1-js/6-objects-more/7-bind/article.md +++ b/1-js/6-objects-more/7-bind/article.md @@ -73,11 +73,31 @@ setTimeout(function() { */!* ``` -Теперь код работает, так как `user` достаётся из замыкания. +Теперь код работает, так как `user` достаётся из замыкания. + +Это решение также позволяет передать дополнительные аргументы: + + +```js +//+ run +var user = { + firstName: "Вася", + sayHi: function(who) { + alert(this.firstName + ": Привет, " + who); + } +}; + +*!* +setTimeout(function() { + user.sayHi("Петя"); // Вася: Привет, Петя +}, 1000); +*/!* +``` + Но тут же появляется и уязвимое место в структуре кода! -**А что, если до срабатывания `setTimeout` в переменную `user` будет записано другое значение? К примеру, какой-то другой пользователь... В этом случае вызов неожиданно будет совсем не тот!** +А что, если до срабатывания `setTimeout` (ведь есть целая секунда) в переменную `user` будет записано другое значение? К примеру, в другом месте кода будет присвоено `user=(другой пользователь)`... В этом случае вызов неожиданно будет совсем не тот! Хорошо бы гарантировать правильность контекста. @@ -87,23 +107,32 @@ setTimeout(function() { ```js function bind(func, context) { - return function() { // (*) + return function() { // (*) return func.apply(context, arguments); }; } ``` -Результатом вызова `bind(func, context)`, как видно из кода, является анонимная функция функция `(*)`, вот она отдельно: +Посмотрим, что она делает, как работает, на таком примере: + +```js +var oldSayHi = user.sayHi; +var sayHi = bind(oldSayHi, user); +``` + +Результатом `bind(oldSayHi, user)`, как видно из кода, будет анонимная функция `(*)`, вот она отдельно: ```js function() { // (*) - return func.apply(context, arguments); + return oldSayHi.apply(user, arguments); }; ``` -Если её вызвать с какими-то аргументами, то она сама ничего не делает, а "передаёт вызов" в `func`. Здесь используется `apply`, чтобы вызвать `func` с теми же аргументами, которые получила эта анонимная функция и с контекстом `context`, который берётся из замыкания (был задан при вызове `bind`). +Она запишется в переменную `sayHi`. -Иными словами, в результате вызова `bind` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с фиксированным контекстом `context`. +Далее, если её вызвать с какими-то аргументами, например `sayHi("Петя")`, то она "передаёт вызов" в `oldSayHi` -- используется `.apply(user, arguments)`, чтобы передать в качестве контекста `user` (он будет взят из замыкания) и текущие аргументы `arguments`. + +Иными словами, в результате вызова `bind(func, context)` мы получаем "функцию-обёртку", которая прозрачно передаёт вызов в `func`, с теми же аргументами, но фиксированным контекстом `context`. Пример с `bind`: @@ -117,7 +146,7 @@ function bind(func, context) { var user = { firstName: "Вася", - sayHi: function() { + sayHi: function() { alert(this.firstName); } }; @@ -127,8 +156,32 @@ setTimeout( bind(user.sayHi, user), 1000 ); */!* ``` -Теперь всё в порядке! В `setTimeout` пошла обёртка, фиксирующая контекст. +Теперь всё в порядке! +Вызов `bind(user.sayHi, user)` возвращает такую функцию-обёртку, которая гарантированно вызовет `user.sayHi` в контексте `user`. В данном случае, через 1000мс. + +Причём, если вызвать обёртку с аргументами -- они пойдут в `user.sayHi` без изменений, фиксирован лишь контекст. + +```js +//+ run +var user = { + firstName: "Вася", + sayHi: function(who) { + alert(this.firstName + ": Привет, " + who); + } +}; + +var sayHi = bind(user.sayHi, user); + +*!* +sayHi("Петя"); // Вася: Привет, Петя +sayHi("Маша"); // Вася: Привет, Маша +*/!* +``` + +В примере выше продемонстрирована другая частая цель использования `bind` -- "привязать" функцию к контексту, чтобы в дальнейшем "не таскать за собой" объект, а просто вызывать `sayHi`. + +Результат `bind` можно передавать в любое место кода, вызывать как обычную функцию, он "помнит" свой контекст. ## Решение 3: встроенный метод bind [#bind] @@ -151,7 +204,7 @@ var wrapper = func.bind(context[, arg1, arg2...])
Если указаны аргументы `arg1, arg2...` -- они будут прибавлены к каждому вызову новой функции, причем встанут *перед* теми, которые указаны при вызове.
-Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, а новые будут уже за ними, но об этом чуть позже. +Результат вызова `func.bind(context)` аналогичен вызову `bind(func, context)`, описанному выше. То есть, `wrapper` -- это обёртка, фиксирующая контекст и передающая вызовы в `func`. Также можно указать аргументы, тогда и они будут фиксированы, но об этом чуть позже. Пример со встроенным методом `bind`: @@ -166,13 +219,14 @@ var user = { *!* // setTimeout( bind(user.sayHi, user), 1000 ); - setTimeout( user.sayHi.bind(user), 1000 ); // аналог через встроенный метод */!* ``` Получили простой и надёжный способ привязать контекст, причём даже встроенный в JavaScript. +Далее мы будем использовать именно встроенный метод `bind`. + [smart header="Привязать всё: `bindAll`"] Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле: @@ -236,8 +290,9 @@ alert( triple(5) ); // = mul(3, 5) = 15 При помощи `bind` мы можем получить из функции её "частный вариант" как самостоятельную функцию и дальше передать в `setTimeout` или сделать с ней что-то ещё. +Наш выигрыш в этом состоит в том, что эта самостоятельная функция, во-первых, имеет понятное имя (`double`, `triple`), а во-вторых, повторные вызовы позволяют не указывать каждый раз первый аргумент, он уже фиксирован благодаря `bind`. -## Функция дла задач +## Функция ask для задач В задачах этого раздела предполагается, что объявлена следующая "функция вопросов" `ask`: @@ -251,7 +306,7 @@ function ask(question, answer, ok, fail) { Её назначение -- задать вопрос `question` и, если ответ совпадёт с `answer`, то запустить функцию `ok()`, а иначе -- функцию `fail()`. -В реальном проекте она будет сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно. +Несмотря на внешнюю простоту, функции такого вида активно используются в реальных проектах. Конечно, они будут сложнее, вместо `alert/prompt` -- вывод красивого JavaScript-диалога с рамочками, кнопочками и так далее, но это нам сейчас не нужно. Пример использования: @@ -272,9 +327,24 @@ function die() { ## Итого -Функции и контекст к JavaScript -- как шнурки и кроссовки. Если мы куда-то отправляем шнурки (например в `setTimeout`), то кроссовки сами за ними не побегут. + [head] ``` diff --git a/2-ui/1-document/7-basic-dom-node-properties/article.md b/2-ui/1-document/7-basic-dom-node-properties/article.md index 11dc99a5..c680fd0f 100644 --- a/2-ui/1-document/7-basic-dom-node-properties/article.md +++ b/2-ui/1-document/7-basic-dom-node-properties/article.md @@ -407,7 +407,7 @@ chatDiv.innerHTML += "Как дела?"; **Иными словами, `elem.textContent` возвращает конкатенацию всех текстовых узлов внутри `elem`.** -Не сказать, чтобы эта информация была часто востребована. +Не сказать, чтобы эта информация была часто востребована. **Гораздо полезнее возможность записать текст в элемент, причём именно как текст!** diff --git a/2-ui/1-document/9-attributes-and-custom-properties/article.md b/2-ui/1-document/9-attributes-and-custom-properties/article.md index f97879ff..62900197 100644 --- a/2-ui/1-document/9-attributes-and-custom-properties/article.md +++ b/2-ui/1-document/9-attributes-and-custom-properties/article.md @@ -372,7 +372,7 @@ div.classList.add('order-state-canceled'); Проще говоря, значение атрибута -- произвольная строка, значение класса -- это "есть" или "нет", поэтому естественно, что атрибуты "мощнее" и бывают удобнее классов как в JS так и в CSS. -## Свойство dataSet, data-атрибуты +## Свойство dataset, data-атрибуты С помощью нестандартных атрибутов можно привязать к элементу данные, которые будут доступны в JavaScript. diff --git a/2-ui/2-events-and-interfaces/2-events-and-timing-depth/article.md b/2-ui/2-events-and-interfaces/2-events-and-timing-depth/article.md index 7d9265ac..5c719796 100644 --- a/2-ui/2-events-and-interfaces/2-events-and-timing-depth/article.md +++ b/2-ui/2-events-and-interfaces/2-events-and-timing-depth/article.md @@ -1,15 +1,15 @@ # Порядок обработки событий -События могут возникать не только по очереди, но и пачкой, по многу сразу. Возможно и такое, что во время обработки одного события возникают другие. +События могут возникать не только по очереди, но и "пачкой" по много сразу. Возможно и такое, что во время обработки одного события возникают другие, например пока выполнялся код для `onclick` -- посетитель провёл мышкой, а это уже `mousemove`. -Здесь и далее, очень важно понимать, как браузер обычно работает с событиями и важные исключения из этого правила. Это мы и разберём в этой главе. +Здесь мы разберём, как браузер обычно работает с одновременно возникающими событиями и какие есть исключения из общего правила. [cut] ## Главный поток В каждом окне выполняется только один *главный* поток, который занимается выполнением JavaScript, отрисовкой и работой с DOM. -Он выполняет команды последовательно и блокируется при выводе модальных окон, таких как `alert`. +Он выполняет команды последовательно, может делать только одно дело одновременно и блокируется при выводе модальных окон, таких как `alert`. [smart header="Дополнительные потоки тоже есть"] @@ -21,9 +21,9 @@ [smart header="Web Workers"] Существует спецификация Web Workers, которая позволяет запускать дополнительные JavaScript-процессы(workers). -Они могут обмениваться сообщениями с главным процессом, но их переменные полностью независимы. +Они могут обмениваться сообщениями с главным процессом, но у них свои переменные, и работают они также сами по себе. -В частности, дополнительные процессы не имеют доступа к DOM, поэтому они полезны, преимущественно, при вычислениях, чтобы загрузить несколько ядер/процессоров одновременно. +Такие дополнительные процессы не имеют доступа к DOM, поэтому они полезны, преимущественно, при вычислениях, чтобы загрузить несколько ядер/процессоров одновременно. [/smart] ## Очередь событий @@ -36,18 +36,17 @@ **Когда происходит событие, оно попадает в очередь.** -Внутри браузера существует главный внутренний цикл, который проверяет очередь и обрабатывает события, запускает соответствующие обработчики и т.п. +Внутри браузера непрерывно работает "главный внутренний цикл", который следит за состоянием очереди и обрабатывает события, запускает соответствующие обработчики и т.п. **Иногда события добавляются в очередь сразу пачкой.** Например, при клике на элементе генерируется несколько событий:
  1. Сначала `mousedown` -- нажата кнопка мыши.
  2. -
  3. Затем `mouseup` -- кнопка мыши отпущена.
  4. -
  5. Так как это было над одним элементом, то дополнительно генерируется `click`
  6. +
  7. Затем `mouseup` -- кнопка мыши отпущена и, так как это было над одним элементом, то дополнительно генерируется `click` (два события сразу).
- +[online] В действии: ```html @@ -61,14 +60,17 @@ area.onclick = function(e) { this.value += "click\n"; this.scrollTop = 1e9; }; ``` +[/online] -Таким образом, при нажатии кнопки мыши в очередь попадёт событие `mousedown`, а при отпускании -- сразу два события: `mouseup` и `click`. Браузер сначала обработает первое, а потом -- второе. +Таким образом, при нажатии кнопки мыши в очередь попадёт событие `mousedown`, а при отпускании -- сразу два события: `mouseup` и `click`. Браузер обработает их строго одно за другим: `mousedown` -> `mouseup` -> `click`. -**При этом каждое событие из очереди обрабатывается полностью отдельно от других.** +При этом каждое событие из очереди обрабатывается полностью отдельно от других. ## Вложенные (синхронные) события -В тех случаях, когда событие инициируется не посетителем, а кодом, то оно, как правило, обрабатывается синхронно, то есть прямо сейчас. +Обычно возникающие события "становятся в очередь". + +Но в тех случаях, когда событие инициируется не посетителем, а кодом, то оно, как правило, обрабатывается синхронно, то есть прямо сейчас. Рассмотрим в качестве примера событие `onfocus`. @@ -96,9 +98,9 @@ ``` -В главе [](/focus-blur) мы познакомимся с этим событием подробнее, а пока -- нажмите на кнопку в примере ниже. При этом обработчик `onclick` вызовет метод `focus()` на текстовом поле `text`. +В главе [](/focus-blur) мы познакомимся с этим событием подробнее, а пока -- нажмите на кнопку в примере ниже. -**Событие `onfocus`, инициированное вызовом `text.focus()`, будет обработано синхронно, прямо сейчас, до завершения `onclick`.** +При этом обработчик `onclick` вызовет метод `focus()` на текстовом поле `text`. Код обработчика `onfocus`, который при этом запустится, сработает синхронно, прямо сейчас, до завершения `onclick`. ```html @@ -121,12 +123,13 @@ ``` -При клике на кнопке в примере выше будет видно, что управление вошло в `onclick`, затем перешло в `onfocus`, затем вышло из `onclick`. +При клике на кнопке в примере выше будет видно, что управление вошло в `onclick`, затем перешло в `onfocus`, затем вышло из `onclick`. -**Так ведут себя все браузеры, кроме IE.** +[warn header="Исключение в IE"] +Так ведут себя все браузеры, кроме IE. В нём событие `onfocus` -- всегда асинхронное, так что будет сначала полностью обработан клик, а потом -- фокус. В остальных -- фокус вызовется посередине клика. Попробуйте кликнуть в IE и в другом браузере, чтобы увидеть разницу. - +[/warn] ## Делаем события асинхронными через setTimeout(...,0) @@ -134,7 +137,7 @@ Можно добиться и этого. -Один вариант -- просто переместить строку `text.focus()` вниз кода обработчика. +Один вариант -- просто переместить строку `text.focus()` вниз кода обработчика `onclick`. Если это неудобно, можно запланировать `text.focus()` чуть позже через `setTimeout(..., 0)`, вот так @@ -173,3 +176,6 @@
  • Синхронными являются вложенные события, инициированные из кода.
  • Чтобы сделать событие гарантированно асинхронным, используется вызов через `setTimeout(func, 0)`.
  • + +Отложенный вызов через `setTimeout(func, 0)` используется не только в событиях, а вообще -- всегда, когда мы хотим, чтобы некая функция `func` сработала после того, как текущий скрипт завершится. + diff --git a/2-ui/2-events-and-interfaces/3-obtaining-event-object/1-move-ball-field/task.md b/2-ui/2-events-and-interfaces/3-obtaining-event-object/1-move-ball-field/task.md index 6387e34f..aac26089 100644 --- a/2-ui/2-events-and-interfaces/3-obtaining-event-object/1-move-ball-field/task.md +++ b/2-ui/2-events-and-interfaces/3-obtaining-event-object/1-move-ball-field/task.md @@ -11,7 +11,7 @@
  • Мяч после перелёта должен становиться центром ровно под курсор мыши, если это возможно без вылета за край поля.
  • CSS-анимация не обязательна, но желательна.
  • Мяч должен останавливаться у границ поля, ни в коем случае не вылетать за них.
  • -
  • При прокрутке страницы ничего не должно ломаться.
  • +
  • При прокрутке страницы с полем ничего не должно ломаться.
  • Замечания: diff --git a/2-ui/2-events-and-interfaces/3-obtaining-event-object/article.md b/2-ui/2-events-and-interfaces/3-obtaining-event-object/article.md index b7ee8645..8e8f472f 100644 --- a/2-ui/2-events-and-interfaces/3-obtaining-event-object/article.md +++ b/2-ui/2-events-and-interfaces/3-obtaining-event-object/article.md @@ -2,12 +2,12 @@ Чтобы хорошо обработать событие, недостаточно знать о том, что это -- "клик" или "нажатие клавиши". Могут понадобиться детали: координаты курсора, введённый символ и другие, в зависимости от события. -**Детали произошедшего браузер записывает в "объект события", который передаётся первым аргументом в обработчик.** +Детали произошедшего браузер записывает в "объект события", который передаётся первым аргументом в обработчик. [cut] -## Получение объекта события +## Свойства объекта события -Пример ниже демонстрирует использования объекта события: +Пример ниже демонстрирует использование объекта события: ```html @@ -15,6 +15,7 @@