JavaScript має унікальний спосіб виконання коду за допомогою Call Stack (стека викликів) та Event Loop (циклу подій), який керує асинхронними задачами, розділеними на Microtasks (мікрозадачі) та Macrotasks (макрозадачі). Давайте розберемо це поетапно:
1. Call Stack (Стек викликів)
- Call Stack – це структура даних, яка відстежує виклики функцій.
- JavaScript є однопоточним (single-threaded), тобто може виконувати лише одну задачу за раз.
- Коли функція викликається, вона додається у стек (push). Після завершення виклику функція видаляється зі стека (pop).
Приклад:
function first() {
console.log("First");
}
function second() {
console.log("Second");
first();
}
second();
Послідовність виконання:
second()
додається у стек.- Всередині
second()
викликаєтьсяfirst()
, яка також додається у стек. console.log("First")
виконується, після чогоfirst()
видаляється зі стека.console.log("Second")
виконується, після чогоsecond()
видаляється зі стека.
2. Event Loop (Цикл подій)
Event Loop забезпечує, щоб Call Stack залишався порожнім перед обробкою відкладених задач. Якщо стек порожній, цикл подій перевіряє Task Queue (чергу задач), яка містить мікрозадачі (Microtasks) та макрозадачі (Macrotasks).
3. Microtasks (Мікрозадачі)
Мікрозадачі мають найвищий пріоритет і виконуються відразу після завершення поточної операції та очищення Call Stack.
Приклади мікрозадач:
- Promises (проміси)
Promise.resolve().then(...)
async/await
(обробляється як мікрозадача).
- MutationObserver
- API для відстеження змін у DOM.
- queueMicrotask()
- Метод для явного планування мікрозадачі.
Основні характеристики:
- Мікрозадачі обробляються раніше макрозадач.
- Усі мікрозадачі в черзі виконуються послідовно перед переходом до наступної макрозадачі.
4. Macrotasks (Макрозадачі)
Макрозадачі – це заплановані задачі, які обробляються після очищення поточного стека та після мікрозадач.
Приклади макрозадач:
- Timers (таймери)
setTimeout
setInterval
- UI Rendering
- Задачі рендерингу браузера, наприклад, reflows і repaints.
-
setImmediate (лише у Node.js)
-
I/O tasks
- Операції введення/виведення, такі як читання або запис файлів.
- MessageChannel
- Для міжпотокового зв’язку.
Основні характеристики:
- Макрозадачі мають нижчий пріоритет, ніж мікрозадачі.
- Лише одна макрозадача виконується після завершення всіх мікрозадач.
5. Приклад суміщення
console.log("Script start");
setTimeout(() => console.log("Macrotask 1"), 0);
Promise.resolve().then(() => {
console.log("Microtask 1");
Promise.resolve().then(() => console.log("Microtask 2"));
});
setTimeout(() => console.log("Macrotask 2"), 0);
console.log("Script end");
Script start
Script end
Microtask 1
Microtask 2
Macrotask 1
Macrotask 2
Послідовність виконання:
console.log("Script start")
виконується (синхронна задача).setTimeout
додає дві макрозадачі.Promise.resolve()
додає мікрозадачу.console.log("Script end")
виконується (синхронна задача).- Виконується черга мікрозадач:
console.log("Microtask 1")
Promise.resolve()
додає ще одну мікрозадачу (console.log("Microtask 2")
).console.log("Microtask 2")
.
- Виконується черга макрозадач:
console.log("Macrotask 1")
console.log("Macrotask 2")
.
6. Підсумок порядку виконання
- Синхронний код (функції та змінні, що виконуються у Call Stack).
- Мікрозадачі:
- Проміси (Promises),
queueMicrotask
, іMutationObserver
. - Функції, що виконуються як частина мікрозадач.
- Макрозадачі:
setTimeout
,setInterval
, задачі введення/виведення тощо.
- Задачі рендерингу інтерфейсу користувача (специфічно для браузера).
Візуалізація виконання
- Call Stack: Виконує синхронний код і виклики функцій.
- Task Queue:
- Microtasks Queue: Черга високопріоритетних задач, таких як проміси.
- Macrotasks Queue: Черга задач із нижчим пріоритетом, таких як
setTimeout
.
- Event Loop: Постійно перевіряє і обробляє черги задач.
Цей процес гарантує, що JavaScript залишається асинхронним і ефективним.
Розуміння цього механізму допоможе вам писати продуктивний та передбачуваний код!
Перекладено з: JavaScript Execution Flow: Call Stack, Microtasks, and Macrotasks