JavaScript Event Loop: Все, что вам нужно знать, объясняется просто

Очереди микрозадач, очереди задач, стеки вызовов, циклы событий и движки JavaScript — что общего между всеми этими запутанными вещами? Ну, все они заставляют JavaScript (JS) работать. А под «заставлять JS работать» я имею в виду, что все они заставляют JS работать в браузере.

Сегодня мы узнаем все о цикле событий в JavaScript! Надеюсь, к концу этой статьи у вас будет базовое понимание того, как работает цикл событий, а также очередей и стеков, которые обеспечивают его работу, так что давайте приступим!

КАК РАБОТАЕТ JAVASCRIPT

Прежде чем мы сможем понять, как работает цикл событий, мы должны понять, как работает JavaScript.

JavaScript является однопоточным языком. Это означает, что программа на JS может выполнять только одно действие за раз. При выполнении сценария JS будет последовательно проходить через каждую строку кода, выполняя каждое утверждение и ожидая его завершения, прежде чем переходить к следующему.

Под капотом этот построчный процесс обрабатывается движком JS. JS Engine — это программное обеспечение, которое отвечает за разбор и выполнение кода JavaScript.

Чтобы отслеживать каждый выполняемый оператор, JS Engine использует так называемый стек вызовов. Это инструмент, который хранит информацию обо всех вызовах, сделанных при завершении выполнения функции.

Когда движок JS Engine вызывает функцию, он помещает ее в стек вызовов, а затем выполняет тело функции. Любые внутренние вызовы функции помещаются в стек и также выполняются.

Как только функция завершена, она выталкивается из стека, и движок переходит к следующей строке кода. Этот процесс продолжается до тех пор, пока движок JS не достигнет последней строки сценария.

Таким образом, JS может сосредоточиться на одной вещи за раз, выполняя ее до конца, а затем переходить к следующей синхронно, одна за другой.

Теперь это звучит замечательно. Это аккуратно и опрятно, и это делает свою работу. Однако это также может создать некоторые проблемы.

Если есть функция или цикл, на выполнение которых уходит буквально целая вечность, программа будет заблокирована и застрянет в этой точке! Остальная часть сценария никогда не будет выполнена.

Блокировка сценария — это большое «нет-нет». Если бы мы выполняли такую задачу, как получение фотографии из другого источника по сети в JS, это заставило бы скрипт приостановиться и ждать, пока фотография придет. Это потенциально может вызвать огромную задержку. А если она так и не придет, скрипт будет заблокирован. Это делает нецелесообразным использование JS для выполнения подобных блокирующих задач.

Как же тогда использовать JS для запуска функции, не блокируя выполнение скрипта? Например, каким-то образом запустить блокирующую функцию параллельно (в одно и то же время), пока движок завершает выполнение остальной части сценария? А как насчет того, что мы не хотим, чтобы это происходило прямо сейчас, а в более позднее время? Например, показать картинку на экране через 3 секунды. Как JS сможет справиться с этим?

Обе эти ситуации являются примерами асинхронных событий, они инициируются в один момент, а завершаются в другое время, позже в будущем. К сожалению, JS является однопоточным и синхронным! Он работает только здесь и сейчас. Он не может делать что-то параллельно и не может обрабатывать будущие события, возвращаясь назад, чтобы выполнить уже пройденный код.

Если это так, то как же JS сможет заставить эти асинхронные события происходить?

Предупреждение — он не может!

По крайней мере, не самостоятельно.

JAVASCRIPT РАБОТАЕТ В СРЕДЕ БРАУЗЕРА

JS может делать только одну вещь за раз, но в браузере мы можем использовать его для одновременного выполнения множества различных действий. И не только это, но мы можем использовать его для отображения различных вещей в разное время. Анимация страницы работает, вещи внезапно всплывают, я могу нажимать на вещи, я могу прокручивать страницу, и ничто никогда не блокируется. Как это возможно?

Ну, это, мой друг, потому что JS не работает в одиночку!

Когда мы пишем JS для создания веб-сайтов, весь JS, который мы пишем, запускается в браузере. Наряду с размещением JS Engine, браузер предоставляет дополнительные инструменты, помогающие JS обрабатывать асинхронные события. Эти инструменты называются Web API.

Если вы еще не знали, то функции типа setTimeOut() и методы типа addEventListener() на самом деле не являются JS! (😮🤭 я знаю!)

Скорее это Web API, которые мы можем использовать в JS для обработки этих событий в другом месте, асинхронно, пока JS Engine делает свое дело.

Как только асинхронное событие готово к выполнению, ему нужно каким-то образом попасть в стек вызовов, чтобы его действительно мог запустить JS Engine.

Эта часть довольно сложна. У вас могут быть разные вещи, завершающиеся в разное время, поэтому то, как все хранится, управляется и отправляется в стек вызовов для выполнения, представляет собой действительно сложную задачу.

Именно здесь на помощь приходит цикл событий!!!

ЧТО ТАКОЕ ЦИКЛ СОБЫТИЙ?

Цикл событий — это процесс, который координирует асинхронные события в браузере.

Цикл событий — это именно то, на что он похож: это цикл, или набор шагов, который планируется выполнять непрерывно, пока открыта веб-страница.

Этот процесс обычно обрабатывается программой в вашем браузере. Например, в Chrome программой, которая обрабатывает цикл событий, является libevent.

КАК ЖЕ РАБОТАЕТ ЦИКЛ СОБЫТИЙ?

Когда асинхронное событие, например setTimeout, завершено и готово к выполнению, функция обратного вызова (она же задача) добавляется в нечто, называемое очередью.

В области информатики очереди очень похожи на стеки. Они обе представляют собой структуры, используемые для хранения данных. Единственное различие между ними — это порядок, в котором вы удаляете данные.

В стопках соблюдается порядок удаления Last In First Out (LIFO). То есть последняя вещь, которая была добавлена (или задвинута) в стек, будет первой удалена (или отброшена). Это похоже на стопку тарелок. Последняя тарелка, добавленная в стопку, будет первой тарелкой, которую возьмут из нее позже.

Очереди, с другой стороны, следуют порядку удаления «первым пришел — первым ушел» (FIFO), как настоящая очередь или очередь людей. Первый человек, который встает в очередь, первым будет обслужен и покинет ее.

В среде браузера существуют различные очереди для разных вещей, но две из них, которые важны для работы цикла событий, — это очередь задач и очередь микрозадач.

Когда задача готова к выполнению, браузер добавляет обратный вызов из Web API либо в очередь задач, либо в очередь микрозадач. То, в какую очередь попадет обратный вызов, зависит от того, насколько важна задача. Задачи, которые мы хотим выполнить как можно быстрее, добавляются в очередь микрозадач. Другие задачи добавляются в очередь задач.

Когда основной сценарий завершит выполнение и стек вызовов будет пуст, браузер вызовет цикл событий для выполнения. Цикл событий выполнит работу по добавлению задач из очереди в стек вызовов, чтобы они могли быть выполнены. Затем он будет ждать, пока эта задача завершится и стек вызовов опустеет, а затем добавит следующую задачу.

Из какой очереди цикл событий будет извлекать события, зависит от того, какие задачи необходимо выполнить. События микрозадач могут быть выполнены в любой момент, как только стек вызовов опустеет, в то время как события задач выполняются только один раз за итерацию цикла.

При каждом запуске цикла будет выполняться только одно событие задачи. В отличие от этого, несколько событий микрозадачи могут быть выполнены в течение одной итерации цикла. Это означает, что микрозадачи могут выполняться между задачами, как это сделано здесь:

Хотя мы начинаем с двух обратных вызовов setTimeout, обратные вызовы promise выполняются перед последним обратным вызовом setTimeout, потому что стек вызовов стал пустым. Затем цикл событий снова выполняет итерацию и последний обратный вызов setTimout выполняется.

Этот процесс будет продолжаться снова и снова, цикл событий будет ждать, пока стек вызовов опустеет, а затем добавлять в стек вызовов различные поставленные в очередь задачи по мере их появления.

Вот и все! Вот как работает цикл событий!

ДАВАЙТЕ ПОДВЕДЕМ ИТОГИ

Вот несколько кратких определений:

  • Стек вызовов — отслеживает выполнение функции
  • JS Engine — разбирает и выполняет JS код (использует стек вызовов)
  • Очередь микрозадач — хранит задачи, которые должны быть вызваны в ближайшем будущем
  • Очередь задач — хранит другие задачи, которые должны быть выполнены позже
  • Цикл событий — координирует процесс передачи обратных вызовов из очередей задач в стек вызовов (это позволяет JS выполнять асинхронные события).

По мере того, как происходят различные асинхронные события, например, когда вы нажимаете на кнопку на веб-странице или когда браузеру требуется повторная визуализация веб-страницы для отображения изменений, эти события добавляются браузером в соответствующие очереди. Затем цикл событий добавляет их в стек вызовов, чтобы выполнить, когда все будет ясно.

Вместе стек вызовов и эти очереди являются тем, что цикл событий использует для своей работы, позволяя JS выполнять асинхронные задачи в браузере.

Довольно аккуратно, да?

СЛОВО ПРЕДОСТЕРЕЖЕНИЯ

Хотя процесс, который я здесь описал, является тем, как браузеры должны запускать цикл событий (согласно спецификации), не все браузеры реализуют этот процесс одинаково. Некоторые браузеры могут переводить события, которые должны быть микрозадачами, в очередь задач. Это приводит к тому, что порядок выполнения задач в разных браузерах может отличаться (подробнее об этом см. здесь).

Имейте это в виду, когда будете разрабатывать что-то для разных браузеров!

УЗНАЙТЕ БОЛЬШЕ О ЦИКЛЕ СОБЫТИЙ

Надеюсь, теперь вы лучше понимаете цикл событий и то, как он работает! Вот несколько дополнительных ресурсов, которые вы можете использовать для получения дополнительной информации или расширения своих знаний.

  • Заметка в блоге Джейка Арчибальда — «Задачи, микрозадачи, очереди и расписания».
  • Выступление Джейка Арчибальда на JSConf — «In The Loop».
  • Выступление Филиппа Робертса на JSConf — «Что вообще такое цикл событий?»
  • Лекция 100Devs — «JavaScript Event Loop For Beginners!»
  • Лидия Хэлли dev.to статья — «JavaScript Visualized: Цикл событий»
  • Александр Златков Статья на Medium — «Как работает JavaScript: Цикл событий и рост Async-программирования + 5 способов улучшить кодирование с помощью async/await»
  • Статья Modern JS Tutorial — «Event loop: микрозадачи и макрозадачи».

Привет! Надеюсь, вам понравилась статья. Меня зовут Ти, и я сейчас пытаюсь найти свою первую работу в качестве инженера-программиста. Я документирую процесс перехода от нуля к герою в своем блоге. Не стесняйтесь следить за моим путешествием ниже!

  • Блог: The Developing Dev
  • Twitter: @teeintech
  • Github: tdo95

Оцените статью
devanswers.ru
Добавить комментарий