Как работает Event Loop в JavaScript?
Event Loop — это механизм в JavaScript, который управляет асинхронными задачами и очередями событий. Он позволяет JavaScript работать в однопоточной модели, обрабатывая асинхронные операции, не блокируя основной поток выполнения.
Как работает Event Loop?
Event Loop — это бесконечный цикл, в котором выполняются обработчики событий. В процессе его работы браузер распределяет задачи по двум основным очередям:
- Микротаски — это задачи, такие как промисы,
queueMicrotask()
,MutationObserver
. - Макротаски — это задачи, такие как
setTimeout
, события, таймеры, запросыfetch
.
Основной порядок выполнения
- Сначала выполняются все синхронные задачи из стека вызовов (Call Stack).
- Затем выполняются все задачи из очереди микротасков. Все микротаски будут выполнены до того, как будет взята следующая макрозадача. Микротаски блокируют Event Loop, пока все не будут выполнены.
- После выполнения всех микротасков очередь очищается.
- Далее из очереди макротасков берется одна макрозадача и выполняется. Макрозадачи, такие как
setTimeout
, ждут, пока все синхронные задачи и микротаски будут завершены. - После выполнения макрозадачи проверяется, нужно ли выполнить перерисовку страницы. Если необходимо, браузер выполняет рендеринг.
- Повторный цикл. Event Loop продолжает обрабатывать задачи, пока не закончится выполнение всех операций.
Структуры данных
-
Call Stack (LIFO — Last In, First Out):
- Стек вызовов содержит все функции, которые выполняются в текущий момент.
- Когда вызывается функция, она добавляется в стек. Если внутри функции вызывается другая функция, она тоже добавляется в стек.
- После выполнения функции она удаляется из стека.
-
Web API:
- Асинхронные операции, такие как
setTimeout
, обработчики событий, запросыfetch
, попадают в Web API. - После завершения работы асинхронной задачи, она не сразу попадает в Call Stack. Вместо этого она помещается в Callback Queue (очередь обратных вызовов).
- Асинхронные операции, такие как
-
Callback Queue (FIFO — First In, First Out):
- Здесь хранятся функции, которые должны быть выполнены после того, как стек вызовов окажется пустым.
- Когда стек вызовов очищается, Event Loop перемещает задачи из Callback Queue в Call Stack и выполняет их.
Микротаски vs. Макротаски
-
Макротаски:
- Пример:
setTimeout
, события (onClick
,onChange
), запросыfetch
. - Эти задачи обрабатываются в порядке очереди, одна за другой, после того как выполняются все синхронные задачи и микротаски.
- Пример:
-
Микротаски:
- Пример: промисы,
queueMicrotask()
,MutationObserver
. - Микротаски выполняются сразу после выполнения синхронных операций и до выполнения макротасок.
- Все микротаски из очереди выполняются перед следующими макрозадачами.
- Пример: промисы,
Пример
console.log("Первое сообщение");
setTimeout(() => {
console.log("Второе сообщение");
}, 0);
Promise.resolve().then(() => console.log("Третье сообщение"));
console.log("Четвертое сообщение");
Что произойдет:
- Первое сообщение выводится первым, потому что это синхронная операция.
setTimeout
помещает свою задачу в очередь макротасок, но она не будет выполнена, пока не будут обработаны все микротаски.Promise.resolve().then(...)
помещает задачу в очередь микротасок и будет выполнена перед следующей макрозадачей.- Четвертое сообщение выводится следующим, так как это синхронный код.
- После завершения синхронного кода, микротаск выполняется (выводится Третье сообщение).
- Второе сообщение выводится последним, так как это макротаска и она выполняется только после всех микротасков.
Важное замечание:
Микротаски имеют приоритет перед макротасками. Поэтому даже если вы используете setTimeout
с задержкой 0, функция будет выполнена после всех микротасков.