Цикл подій JavaScript є одним з найосновніших, але водночас найбільш непорозумілих концептів у фронтенд-розробці. Це основа того, як JavaScript обробляє асинхронні операції, забезпечуючи неблокуючу поведінку в однопоточному середовищі. Цей посібник розглядає тонкощі циклу подій, його складові та те, як розуміння цього механізму може допомогти розробникам писати ефективний і безпомилковий код.
Основні компоненти циклу подій
- Стек викликів (Call Stack): Стек викликів — це структура даних, що зберігає відомості про викликані функції. Функції додаються до стека під час виклику і видаляються після завершення виконання. Оскільки JavaScript однопоточний, лише одна функція може виконуватися в будь-який момент часу.
- Web API: Браузерні API, такі як
setTimeout
, маніпулювання DOM, іfetch
, працюють асинхронно і передають завдання в цикл подій для виконання після завершення. - Черга мікротасків (Microtask Queue): Обробляє задачі високого пріоритету, такі як вирішені Promises і
MutationObserver
. Ці задачі обробляються перед чергою макротасків. - Черга макротасків (Macrotask Queue): Містить задачі типу
setTimeout
,setInterval
та обробники подій (Event Listener). Ці задачі обробляються після очищення черги мікротасків. - Цикл подій (Event Loop): Цикл подій — це оркестратор, який забезпечує виконання задач у правильному порядку. Він перевіряє стек викликів і черги завдань, переміщаючи задачі з черг у стек, коли він порожній.
Як це працює
Цикл подій постійно моніторить стек викликів. Якщо стек порожній, він спочатку обробляє задачі з черги мікротасків. Як тільки черга мікротасків порожня, він переходить до черги макротасків.
console.log("Script start");
setTimeout(() => {
console.log("Macrotask: setTimeout");
}, 0);
Promise.resolve().then(() => {
console.log("Microtask: Promise");
});
console.log("Script end");
Покрокове виконання
- Стек викликів (Call Stack): Спочатку виконується
console.log("Script start")
, виводячи "Script start". - Черга макротасків (Macrotask Queue): Колбек
setTimeout
додається до черги макротасків. - Черга мікротасків (Microtask Queue): Розв'язка
Promise
додає свій колбек.then
до черги мікротасків. - Стек викликів (Call Stack): Виконується
console.log("Script end")
, виводячи "Script end". - Виконання мікротасків (Microtasks Executed): Черга мікротасків обробляє колбек
Promise
, виводячи "Microtask: Promise". - Виконання макротасків (Macrotasks Executed): Нарешті обробляється колбек
setTimeout
з черги макротасків, виводячи "Macrotask: setTimeout".
Більш складний сценарій
Давайте додамо вкладені мікротаски та макротаски, щоб побачити їхню взаємодію:
console.log("Start");
setTimeout(() => {
console.log("Macrotask 1: setTimeout");
Promise.resolve().then(() => {
console.log("Microtask 1: Nested Promise");
});
}, 0);
Promise.resolve().then(() => {
console.log("Microtask 2: Promise");
});
console.log("End");
Покрокове виконання
- Стек викликів (Call Stack):
console.log("Start")
виконується, виводячи "Start". - Черга макротасків (Macrotask Queue): Колбек
setTimeout
додається до черги макротасків. - Черга мікротасків (Microtask Queue): Перше виконання
Promise
додає свій колбек.then
до черги мікротасків. - Стек викликів (Call Stack):
console.log("End")
виконується, виводячи "End". - Виконання мікротасків (Microtasks Executed): Перший мікротаск виконується, виводячи “Microtask 2: Promise”.
- Виконання макротасків (Macrotasks Executed): Колбек
setTimeout
обробляється, виводячи "Macrotask 1: setTimeout".
Всередині цього макротаска додається новий мікротаск (“Microtask 1: Nested Promise”) до черги мікротасків.
7.
Виконання вкладених мікротасків (Nested Microtasks Executed): Вкладений мікротаск виконується, виводячи “Microtask 1: Nested Promise”.
виведення
Поширені непорозуміння
- Нульова затримка в
setTimeout
: ВстановленняsetTimeout
з затримкою0
не означає, що колбек виконається негайно. Він додається до черги макротасків і виконується після очищення поточного стека викликів та всіх мікротасків. - Блокуючий код: Довготривалі синхронні операції блокують стек викликів, перешкоджаючи виконанню інших задач. Розуміння цієї поведінки допомагає уникнути зависання інтерфейсу.
Цикл подій в React.js
В React цикл подій є важливим для:
- Оновлення стану (State Updates): Забезпечення пакетного оновлення та асинхронного застосування змін.
- Рендеринг (Rendering): Оптимізація циклів рендерингу за допомогою
requestAnimationFrame
. - Ефекти (Effects): Ефективне управління
useEffect
та функціями очищення.
Налагодження циклу подій
Інструменти, такі як Chrome DevTools, дозволяють розробникам аналізувати цикл подій. Вкладка Performance надає хронологічний вигляд завдань, мікротасків і подій рендерингу. Використовуйте цей інструмент для виявлення вузьких місць і оптимізації продуктивності.
Поради з оптимізації продуктивності
- Розбивайте довгі завдання: Використовуйте
requestIdleCallback
або розділяйте завдання на менші частини, щоб уникнути блокування інтерфейсу. - Зменшуйте повторні обчислення (Reflows): Уникайте надмірної маніпуляції DOM у швидкій послідовності, щоб знизити накладні витрати на рендеринг.
- Використовуйте асинхронні API: Віддавайте перевагу Promises і
async/await
для більш чистого та ефективного асинхронного коду.
Висновок
Глибоке розуміння циклу подій дозволяє фронтенд-розробникам створювати додатки, які не лише функціональні, але й високо продуктивні та відгукуються швидко. Використовуючи можливості циклу подій і оптимізуючи управління завданнями, ви можете забезпечити безперервний користувацький досвід.
Джерела
- MDN Web Docs: Цикл подій (Event Loop)
- You Don’t Know JS авторства Kyle Simpson
- Документація Chrome DevTools
Перекладено з: Understanding the JavaScript Event Loop: A Comprehensive Guide for Front-End Developers