Event Loop
Параллелизм в JavaScript основывается на модели "событийного цикла". Эта модель отличается от модели других языков, например C или Java.
Стек
Вызов любой функции создает контекст выполнения. При вызове вложенной функции создается новый контекст, а старый сохраняется в специальной структуре. Так формируется стек контекстов.
Когда вызываетсяg
, создаётся область видимости, содержащая аргументыg
и локальные переменные. Когдаg
вызываетf
, создается вторая область видимости и помещается в стек вперед первой, которая содержит аргументыf
и ее локальные переменные. Когдаf
возвращает результат, верхний элемент из стека удаляется. Когдаg
возвращает результат, ее контекст также удалится, и стек будет пуст.
Куча
Объекты размещаются в куче. Куча — это ссылка на определенную неструктурированную область памяти.
Очередь
Среда выполнения JavaScript содержит очередь событий. Очередь событий — это список событий, подлежащих обработке. Каждое событие ассоциируется с некоторой функцией. Когда на стеке освобождается достаточно места, событие извлекается из очереди и обрабатывается. Обработка события состоит в вызове этой функции (и таким образом создании начального контекста выполнения). Обработка события заканчивается, когда стек снова становится пустым.
Цикл событий
Модель событийного цикла (event loop
) называется так потому, что отслеживает новые события в цикле:
queue.waitForMessage
ожидает события, если очередь пуста.
Запуск до завершения
Каждое событие должно быть полностью обработано прежде, чем начнет обрабатываться следующее. Благодаря этому мы можем точно знать: когда бы и где бы ни выполнялась функция – она не может быть приостановлена и будет целиком завершена до начала выполнения другого кода (который может изменять данные, с которыми работает текущая функция). Это отличает JavaScript от такого языка программирования, как C. Поскольку в С функция, запущенная в отдельном потоке, в любой момент может быть остановлена, чтобы выполнить какой-то другой код в другом потоке.
У данного подхода есть и минусы. Если событие обрабатывается слишком долго, то приложение в это время не имеет возможности обработать действия пользователя (например, скролл или клик). Браузер может смягчать последствия такой проблемы. Internet Explorer в данном случае выводит сообщение "A script on this page is causing Internet Explorer to run slowly" и предлагает завершить обработку слишком тяжелого события. Хорошая практика для предотвращения этого – не создавать события, которые могут выполняться долго и разбивать большие события на несколько мелких.
Добавление событий в очередь
В браузерах события добавляются в очередь в любое время, если событие произошло, а так же если у него есть обработчик. В случае, если обработчика нет – событие потеряно. Так, клик по элементу, имеющему обработчик события по событиюclick
, добавит событие в очередь, а если обработчика нет – то и событие в очередь не попадет.
Вызов setTimeout добавит событие в очередь по прошествии времени, указанного во втором аргументе вызова. Если очередь событий на тот момент будет пуста, то событие обработается сразу же, в противном случае событию функции setTimeout
придется ожидать завершения обработки остальных событий в очереди. Именно поэтому второй аргумент setTimeout
корректно считать не временем, через которое выполнится функция из первого аргумента, а минимальное время, через которое она сможет выполниться.
Нулевые задержки
Нулевая задержка не дает гарантии, что обработчик выполнится через ноль миллисекунд. Вызов setTimeout
с аргументом 0 (ноль) не завершится за указанное время. Выполнение зависит от количества ожидающих задач в очереди. Например, сообщение ''this is just a message'' из примера ниже будет выведено на консоль раньше, чем произойдет выполнение обработчика сb1. Это произойдет, потому что задержка – это минимальное время, которое требуется среде выполнения на обработку запроса.
Связь нескольких потоков между собой
Web Worker или кросс-доменный фрейм имеют свой собственный стек, кучу и очередь событий. Два отдельных событийных потока могут связываться друг с другом только через отправку сообщений с помощью метода postMessage
.
Этот метод добавляетсообщение
в очередь другого, если он конечно принимает их.
Никогда не блокируется
Очень интересное свойство цикла событий в JavaScript, что, в отличие от множества других языков, поток выполнения никогда не блокируется. Обработка I/O обычно осуществляется с помощью событий и функций обратного вызова, поэтому даже когда приложение ожидает запрос от IndexedDB или ответ от XHR, оно может обрабатывать другие процессы, например: пользовательский ввод.
Существуют хорошо известные исключения как alert
или синхронный XHR, но считается хорошей практикой избегать их использования.
Last updated