en.javascript.info/2-ui/2-events-and-interfaces/2-events-and-timing-depth/article.md
Ilya Kantor 87bf53d076 update
2014-11-16 01:40:20 +03:00

175 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Порядок обработки событий
События могут возникать не только по очереди, но и пачкой, по многу сразу. Возможно и такое, что во время обработки одного события возникают другие.
Здесь и далее, очень важно понимать, как браузер обычно работает с событиями и важные исключения из этого правила. Это мы и разберём в этой главе.
[cut]
## Главный поток
В каждом окне выполняется только один *главный* поток, который занимается выполнением JavaScript, отрисовкой и работой с DOM.
Он выполняет команды последовательно и блокируется при выводе модальных окон, таких как `alert`.
[smart header="Дополнительные потоки тоже есть"]
Есть и другие, служебные потоки, например, для сетевых коммуникаций.
Поэтому скачивание файлов может продолжаться пока главный поток ждёт реакции на `alert`. Но управлять служебными потоками мы не можем.
[/smart]
[smart header="Web Workers"]
Существует спецификация <a href="http://www.w3.org/TR/workers/">Web Workers</a>, которая позволяет запускать дополнительные JavaScript-процессы(workers).
Они могут обмениваться сообщениями с главным процессом, но их переменные полностью независимы.
В частности, дополнительные процессы не имеют доступа к DOM, поэтому они полезны, преимущественно, при вычислениях, чтобы загрузить несколько ядер/процессоров одновременно.
[/smart]
## Очередь событий
Произошло одновременно несколько событий или во время работы одного случилось другое -- как главному потоку обработать это?
Если главный поток прямо сейчас занят, то он не может срочно выйти из середины одной функции и прыгнуть в другую. А потом третью. Отладка при этом могла бы превратиться в кошмар, потому что пришлось бы разбираться с совместным состоянием нескольких функций сразу.
Поэтому используется альтернативный подход.
**Когда происходит событие, оно попадает в очередь.**
Внутри браузера существует главный внутренний цикл, который проверяет очередь и обрабатывает события, запускает соответствующие обработчики и т.п.
**Иногда события добавляются в очередь сразу пачкой.**
Например, при клике на элементе генерируется несколько событий:
<ol>
<li>Сначала `mousedown` -- нажата кнопка мыши.</li>
<li>Затем `mouseup` -- кнопка мыши отпущена.</li>
<li>Так как это было над одним элементом, то дополнительно генерируется `click`</li>
</ol>
В действии:
```html
<!--+ autorun -->
<textarea rows="6" cols="40" id="area">Кликни меня
</textarea>
<script>
area.onmousedown = function(e) { this.value += "mousedown\n"; this.scrollTop = 1e9; };
area.onmouseup = function(e) { this.value += "mouseup\n"; this.scrollTop = 1e9; };
area.onclick = function(e) { this.value += "click\n"; this.scrollTop = 1e9; };
</script>
```
Таким образом, при нажатии кнопки мыши в очередь попадёт событие `mousedown`, а при отпускании -- сразу два события: `mouseup` и `click`. Браузер сначала обработает первое, а потом -- второе.
**При этом каждое событие из очереди обрабатывается полностью отдельно от других.**
## Вложенные (синхронные) события
В тех случаях, когда событие инициируется не посетителем, а кодом, то оно, как правило, обрабатывается синхронно, то есть прямо сейчас.
Рассмотрим в качестве примера событие `onfocus`.
### Пример: событие onfocus
Когда посетитель фокусируется на элементе, возникает событие `onfocus`. Обычно оно происходит, когда посетитель кликает на поле ввода, например:
```html
<!--+ run autorun -->
<p>При фокусе на поле оно изменит значение.</p>
<input type="text" onfocus="this.value = 'Фокус!'" value="Кликни меня">
```
Но ту же фокусировку можно вызвать и явно, вызовом метода `elem.focus()`:
```html
<!--+ run -->
<input type="text" id="elem" onfocus="this.value = 'Фокус!'">
<script>
*!*
// сфокусируется на input и вызовет обработчик onfocus
elem.focus();
*/!*
</script>
```
В главе [](/focus-blur) мы познакомимся с этим событием подробнее, а пока -- нажмите на кнопку в примере ниже. При этом обработчик `onclick` вызовет метод `focus()` на текстовом поле `text`.
**Событие `onfocus`, инициированное вызовом `text.focus()`, будет обработано синхронно, прямо сейчас, до завершения `onclick`.**
```html
<!--+ autorun -->
<input type="button" id="button" value="Нажми меня">
<input type="text" id="text" size="60">
<script>
button.onclick = function() {
text.value += ' ->в onclick ';
text.focus(); // вызов инициирует событие onfocus
text.value += ' из onclick-> ';
};
text.onfocus = function() {
text.value += ' !focus! ';
};
</script>
```
При клике на кнопке в примере выше будет видно, что управление вошло в `onclick`, затем перешло в `onfocus`, затем вышло из `onclick`.
**Так ведут себя все браузеры, кроме IE.**
В нём событие `onfocus` -- всегда асинхронное, так что будет сначала полностью обработан клик, а потом -- фокус. В остальных -- фокус вызовется посередине клика. Попробуйте кликнуть в IE и в другом браузере, чтобы увидеть разницу.
## Делаем события асинхронными через setTimeout(...,0)
А что, если мы хотим, чтобы *сначала* закончилась обработка `onclick`, а потом уже произошла обработка `onfocus` и связанные с ней действия?
Можно добиться и этого.
Один вариант -- просто переместить строку `text.focus()` вниз кода обработчика.
Если это неудобно, можно запланировать `text.focus()` чуть позже через `setTimeout(..., 0)`, вот так
```html
<!--+ autorun -->
<input type="button" id="button" value="Нажми меня">
<input type="text" id="text" size="60">
<script>
button.onclick = function() {
text.value += ' ->в onclick ';
*!*
setTimeout(function() {
text.focus(); // сработает после onclick
}, 0);
*/!*
text.value += ' из onclick-> ';
};
text.onfocus = function() {
text.value += ' !focus! ';
};
</script>
```
Такой вызов обеспечит фокусировку через минимальный "тик" таймера, по стандарту равный 4мс. Обычно такая задержка не играет роли, а необходимую асинхронность мы получили.
## Итого
<ul>
<li>JavaScript выполняется в едином потоке. Современные браузеры позволяют порождать подпроцессы <a href="http://www.w3.org/TR/workers/">Web Workers</a>, они выполняются параллельно и могут отправлять/принимать сообщения, но не имеют доступа к DOM.</li>
<li>Обычно события становятся в очередь и обрабатываются в порядке поступления, асинхронно, независимо друг от друга.</li>
<li>Синхронными являются вложенные события, инициированные из кода.</li>
<li>Чтобы сделать событие гарантированно асинхронным, используется вызов через `setTimeout(func, 0)`.</li>
</ul>