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

Как вы думаете, что произойдет, если вы запустите эти куски кода в браузере?

Если вы затрудняетесь ответить на поставленные выше вопросы, эта статья для вас.

Код каждой HTML-страницы в браузере выполняется в основном потоке. Основной поток — это основной поток, в котором браузер выполняет JS, выполняет перерисовку, обрабатывает действия пользователя и многое другое. По сути, именно здесь движок JS интегрируется в браузер.

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

  • Personal — выполнение основного кода JavaScript на сайте (далее будем считать, что он уже выполнен)
  • Задания от заказчиков — Рендер, Микрозадачи и Задачи

Скорее всего, личные задачи будут вашим приоритетом. Event Loop согласен с этим. Осталось упорядочить задачи от заказчика. Конечно, первое, что приходит в голову, — это расставить приоритеты у каждого клиента и выстроить их в очередь. Второй — определить, как именно будут обрабатываться задачи от каждого заказчика — по одной, все сразу, а может быть и группами.

На основе этой схемы построена вся работа Event Loop. После того, как мы начали выполнение скрипта, задача с выполнением этого скрипта ставится в Очередь задач. По мере выполнения этого кода мы сталкиваемся с задачами от разных клиентов, которые помещаются в соответствующие очереди. После выполнения задачи на выполнение скрипта (задачи из Задач) Цикл событий переходит к Микрозадачам (после задачи из Задач Цикл событий берет задачи из Микрозадач ). Цикл событий забирает у него задачи, пока они не закончатся. Это означает, что если время их добавления равно времени их выполнения, то Цикл событий будет обрабатывать их бесконечно.

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

Если у одного из клиентов не было задач, то Цикл событий просто переходит к следующему. И наоборот, если задачи занимают много времени у заказчика, то остальные заказчики будут ждать своей очереди. А если задачи от какого-то заказчика оказались бесконечными, то Стек вызовов переполняется, и браузер начинает выдавать окно с ошибкой.

Теперь, когда мы понимаем, как работает Цикл событий, пришло время выяснить, что происходит после выполнения фрагментов кода в начале этой статьи.

Мы видим, что функция foo рекурсивно вызывает себя через setTimeout внутри, но каждый раз, когда она вызывается, она создает задачу клиента Tasks. Как мы помним, в Цикле событий при выполнении очереди задач из Задач в цикл берется только 1 задача. А дальше идет выполнение задач из Микрозадач и Рендеринг. Таким образом, этот фрагмент кода не заставит Цикл событий страдать и будет обрабатывать свои задачи вечно. Но это будет подбрасывать новую задачу для клиента Задачи на каждом круге.

Попробуем выполнить этот скрипт в браузере Google Chrome. Для этого давайте создадим простой HTML-документ и включим в него script.js с этим фрагментом кода. Открыв документ, перейдите в инструменты разработчика, откройте вкладку «Производительность» и нажмите там кнопку «начать профилирование и перезагрузить страницу»:

Мы видим, что задачи из Tasks выполняются по одной в цикле, примерно раз в 4мс.

Рассмотрим вторую проблему:

Здесь мы видим то же самое, что и в примере выше, но вызов bar добавляет задачи из Микрозадач, и все они выполняются, пока не закончатся. А это значит, что пока Event Loop не завершит их, он не сможет перейти к следующему клиенту.

Мы видим, что микрозадачи выполняются примерно раз в 0,1 мс, и это в 40 раз быстрее, чем очередь задач. Все потому, что они выполняются все сразу. В нашем примере очередь движется бесконечно. Для наглядности я сократил его до 100 000 итераций.

Надеюсь, это было полезно для вас!

Спасибо за прочтение! До скорой встречи. 😊