← Назад к вопросам
В какой структуре данных хранятся синхронные операции
1.8 Middle🔥 111 комментариев
#JavaScript Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Структуры данных для синхронных операций в Event Loop
Что такое Event Loop?
Event Loop — это механизм в JavaScript, который обрабатывает синхронный и асинхронный код. Синхронные операции хранятся в специальных очередях (queues) и стеках (stack).
1. Call Stack (стек вызовов)
Call Stack — это основная структура для синхронного кода. Это стек (LIFO — Last In, First Out), где хранятся активные функции во время их выполнения.
function first() {
console.log('first start');
second();
console.log('first end');
}
function second() {
console.log('second');
third();
}
function third() {
console.log('third');
}
first();
// Порядок в Call Stack:
// 1. global -> first -> second -> third
// 2. global -> first (after third)
// 3. global (after first)
Визуализация стека:
Вызов first() Вызов second() Вызов third()
| third | | third | | |
| second| | second | | |
| first | | first | | |
| global | | global | | global |
-------- -------- ----
2. Task Queue (очередь микротаск)
Task Queue (Microtask Queue) — это очередь (FIFO — First In, First Out), в которой хранятся микротаски:
- Promise callbacks (.then, .catch, .finally)
- MutationObserver
- queueMicrotask()
- async/await (это Promise под капотом)
console.log('1: synchronous');
Promise.resolve()
.then(() => console.log('2: microtask'));
console.log('3: synchronous');
// Результат:
// 1: synchronous
// 3: synchronous
// 2: microtask
3. Callback Queue (очередь макротаск)
Callback Queue (Macrotask Queue) — это очередь (FIFO) для макротаск:
- setTimeout / setInterval
- setImmediate (Node.js)
- requestAnimationFrame
- I/O операции
- UI события (click, scroll и т.д.)
console.log('1: start');
setTimeout(() => {
console.log('2: setTimeout');
}, 0);
console.log('3: end');
// Результат:
// 1: start
// 3: end
// 2: setTimeout
Event Loop: Полный цикл
Вот как работает Event Loop пошагово:
console.log('1: Sync code');
setTimeout(() => {
console.log('2: Macrotask (setTimeout)');
}, 0);
Promise.resolve()
.then(() => console.log('3: Microtask (Promise)'));
console.log('4: Sync code');
// Порядок выполнения:
// 1. "1: Sync code"
// 2. "4: Sync code"
// 3. "3: Microtask (Promise)" <- Все микротаски
// 4. "2: Macrotask (setTimeout)" <- Макротаска
Визуализация Event Loop:
┌─────────────────────────────────────┐
│ JavaScript Engine │
│ ┌──────────────────────────────┐ │
│ │ Call Stack │ │ <- Выполняет синхронный код
│ │ [function1, function2, ...] │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Web APIs / Node.js APIs │
│ (timers, fetch, events, etc.) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Microtask Queue │
│ [Promise.then, queueMicrotask] │ <- Выполняется после Call Stack
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Callback Queue (Macrotask) │
│ [setTimeout, setInterval, events] │ <- После всех микротаск
└─────────────────────────────────────┘
4. Порядок выполнения (очень важно!)
function test() {
// Уровень 1: Синхронный код (Call Stack)
console.log('1');
// Уровень 3: Микротаски (после Call Stack)
Promise.resolve().then(() => console.log('2'));
// Уровень 2: Макротаска (после всех микротаск)
setTimeout(() => console.log('3'), 0);
// Уровень 1: Синхронный код (Call Stack)
console.log('4');
}
test();
// Результат:
// 1 <- Call Stack
// 4 <- Call Stack
// 2 <- Microtask Queue (Promise)
// 3 <- Callback Queue (setTimeout)
5. Более сложный пример
console.log('Start');
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => console.log('Promise in setTimeout'));
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1');
setTimeout(() => console.log('setTimeout in Promise'), 0);
})
.then(() => console.log('Promise 2'));
console.log('End');
// Порядок:
// Start <- Синхронный код
// End <- Синхронный код
// Promise 1 <- Микротаска (Promise)
// Promise 2 <- Микротаска (Promise.then)
// setTimeout 1 <- Макротаска
// Promise in setTimeout <- Микротаска ВНУТРИ макротаски
// setTimeout in Promise <- Макротаска (была в микротаске)
Структуры данных в разных частях Event Loop
| Часть | Структура | Правило | Примеры |
|---|---|---|---|
| Call Stack | Стек (LIFO) | Последний вызванный выполняется первым | function calls |
| Microtask Queue | Очередь (FIFO) | Первый добавленный выполняется первым | Promise, async/await |
| Callback Queue | Очередь (FIFO) | Первый добавленный выполняется первым | setTimeout, events |
Практический пример: микротаски выполняются перед макротасками
// Макротаска
setTimeout(() => {
console.log('Macrotask 1');
// Микротаска внутри макротаски
Promise.resolve().then(() => console.log('Microtask inside Macrotask'));
console.log('Still Macrotask 1');
}, 0);
// Микротаска
Promise.resolve()
.then(() => {
console.log('Microtask 1');
// Макротаска внутри микротаски
setTimeout(() => console.log('Macrotask inside Microtask'), 0);
});
// Результат:
// Microtask 1
// Macrotask 1
// Still Macrotask 1
// Microtask inside Macrotask
// Macrotask inside Microtask
Ключевые правила
- Call Stack выполняет весь синхронный код первым
- Microtask Queue выполняется целиком, прежде чем начнётся следующая макротаска
- Callback Queue (Macrotask) выполняет одну макротаску, затем проверяет микротаски снова
- Нет гарантии для setTimeout(fn, 0) — это может быть даже медленнее 4мс из-за браузера
Практическое применение
Если нужна максимальная производительность, используй микротаски (Promise):
// Быстрее
Promise.resolve().then(() => {
console.log('выполнится раньше');
});
// Медленнее (минимум 4мс задержка в браузере)
setTimeout(() => {
console.log('выполнится позже');
}, 0);