Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
RunLoop (Цикл выполнения)
RunLoop — это концепция, которая чаще всего применяется в iOS/macOS разработке. Однако, аналог RunLoop существует и в JavaScript — это Event Loop. В контексте Frontend Development мы говорим об Event Loop, который работает как RunLoop в браузере.
RunLoop в iOS (для контекста)
В iOS/macOS RunLoop — это бесконечный цикл, который обрабатывает события приложения:
while (true) {
// 1. Проверь входящие события (тапы, клики, sensor события)
// 2. Обработай синхронный код
// 3. Отправь результаты в UI
// 4. Жди следующее событие
}
Event Loop в JavaScript (веб-аналог)
В браузере Event Loop работает как RunLoop и выглядит так:
// Упрощенное представление Event Loop
while (eventLoop.waitForTask()) {
// 1. Выполни макротаск (setTimeout, fetch, user events)
if (macrotaskQueue.hasTasks()) {
const macrotask = macrotaskQueue.pop();
macrotask.execute();
}
// 2. Выполни все микротаски (Promise, queueMicrotask)
while (microtaskQueue.hasTasks()) {
const microtask = microtaskQueue.pop();
microtask.execute();
}
// 3. Перерендери UI если нужно
if (needsRepaint) {
render();
}
// 4. Жди следующее событие
}
Этапы Event Loop (в браузере)
1. Макротаски (Macrotasks)
Макротаски выполняются одна за раз, затем проверяются микротаски:
// Примеры макротасков:
- setTimeout/setInterval
- fetch (network requests)
- User events (click, scroll, etc.)
- requestAnimationFrame
- HTML parsing
- DOM manipulation
Пример:
setTimeout(() => {
console.log('Макротаск 1');
}, 0);
setTimeout(() => {
console.log('Макротаск 2');
}, 0);
console.log('Синхронный код');
// Вывод:
// Синхронный код
// Макротаск 1
// Макротаск 2
2. Микротаски (Microtasks)
Микротаски выполняются ДО переходом к следующему макротаску:
// Примеры микротасков:
- Promise.then/catch/finally
- queueMicrotask()
- async/await
- MutationObserver
Пример:
console.log('1');
Promise.resolve()
.then(() => console.log('2'));
setTimeout(() => {
console.log('3');
}, 0);
console.log('4');
// Вывод:
// 1
// 4
// 2
// 3
Полная визуализация Event Loop
Call Stack Event Loop Flow
┌───────────┐
│ Global │ 1. Выполни синхронный код (Call Stack)
├───────────┤ 2. Call Stack пуст?
│ function │ Да -> перейди к шагу 3
└───────────┘ Нет -> продолжи выполнение
3. Выполни все микротаски (Promise, queueMicrotask)
Microtask Queue 4. Есть макротаски?
┌───────────┐ Да -> выполни ОДИН макротаск
│ Promise │ Нет -> жди новое событие
│ qMicro │ 5. Вернись к шагу 2
└───────────┘
Macrotask Queue
┌───────────┐
│ setTimeout│
│ fetch │
│ events │
└───────────┘
Практический пример: порядок выполнения
console.log('Начало');
// Макротаск
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => console.log('Promise внутри setTimeout'));
}, 0);
// Микротаск
Promise.resolve()
.then(() => {
console.log('Promise 1');
})
.then(() => {
console.log('Promise 2');
});
// Макротаск
setTimeout(() => {
console.log('setTimeout 2');
}, 0);
// Синхронный код
console.log('Конец');
/* ВЫВОД:
Начало
Конец
Promise 1
Promise 2
setTimeout 1
Promise внутри setTimeout
setTimeout 2
ОБЪЯСНЕНИЕ:
1. console.log('Начало') -> синхронно
2. setTimeout добавляется в macrotask queue
3. Promise добавляется в microtask queue
4. setTimeout добавляется в macrotask queue
5. console.log('Конец') -> синхронно
6. Call Stack пуст -> проверяем microtask queue
7. Выполняем все микротаски (оба Promise)
8. Выполняем ОДИН макротаск (setTimeout 1)
9. Проверяем microtask queue -> выполняем Promise
10. Выполняем ОДИН макротаск (setTimeout 2)
*/
Пример с fetch (асинхронный запрос)
console.log('Start');
fetch('/api/data')
.then(response => response.json())
.then(data => console.log('Data:', data));
setTimeout(() => {
console.log('Timeout');
}, 0);
console.log('End');
/* ВЫВОД (примерно):
Start
End
Data: {...} <- Promise микротаск выполнен
Timeout <- setTimeout макротаск выполнен
ОБЪЯСНЕНИЕ:
1. fetch запускает сетевой запрос (макротаск)
2. .then() добавляется в microtask queue
3. setTimeout добавляется в macrotask queue
4. После сетевого ответа, микротаски выполняются ПЕРЕД setTimeout
*/
requestAnimationFrame (рисование)
requestAnimationFrame (RAF) выполняется ПЕРЕД переходом к следующему макротаску:
console.log('1');
requestAnimationFrame(() => {
console.log('RAF 1');
});
setTimeout(() => {
console.log('Timeout');
requestAnimationFrame(() => {
console.log('RAF inside Timeout');
});
}, 0);
// Вывод:
// 1
// RAF 1
// Timeout
// RAF inside Timeout
Практический пример: оптимизация производительности
// Плохо: блокирует Event Loop
function slowSync() {
for (let i = 0; i < 1000000000; i++) {
// Тяжелые вычисления
}
}
slowSync(); // Страница зависает
// Хорошо: разбиваешь на части
function slowAsync() {
return new Promise((resolve) => {
setTimeout(() => {
for (let i = 0; i < 100000000; i++) {
// Часть вычислений
}
resolve();
}, 0);
});
}
await slowAsync(); // Event Loop может обработать события
// Или с requestIdleCallback
requestIdleCallback(() => {
// Выполняется когда браузер не занят
});
Запомни очередность
1. Синхронный код (Call Stack)
↓
2. Микротаски (Promise, queueMicrotask)
↓
3. Рисование (репаинт, рефлоу) ← requestAnimationFrame
↓
4. ОДИН Макротаск (setTimeout, fetch, события)
↓
5. GOTO Шаг 1
Инструменты для отладки Event Loop
В Chrome DevTools:
- Вкладка Performance — видишь все события и время выполнения
- Console — может показать что выполняется в какой момент
// Логирование Event Loop
console.time('Event Loop');
console.timeEnd('Event Loop');
Заключение
Event Loop (веб-аналог iOS RunLoop) — это сердце браузера. Он управляет:
- Выполнением синхронного кода
- Обработкой асинхронных операций (Promise, setTimeout)
- Рисованием UI
- Обработкой пользовательских событий
Понимание Event Loop критично для оптимизации производительности, избежания блокировки UI и написания эффективного асинхронного кода в JavaScript.