JavaScript однопоточный — он выполняет только одну задачу за раз. Event Loop — это механизм, который позволяет JS быть асинхронным: пока выполняется синхронный код, асинхронные операции (таймеры, fetch) обрабатываются браузером/Node.js в фоне, а их колбэки ставятся в очередь. Event Loop следит за тем, чтобы очередь задач выполнялась, когда стек вызовов пуст. Ключевое: микрозадачи (Promise.then) всегда выполняются перед макрозадачами (setTimeout).
Работает по принципу LIFO (Last In, First Out). Каждый вызов функции добавляет **фрейм** в стек. Когда функция завершается — фрейм удаляется.
function c() { /* ... */ }
function b() { c() }
function a() { b() }
a()
Стек (растёт вниз):
│ c() │ ← выполняется сейчас
│ b() │
│ a() │
│ main │
└──────┘Когда стек **пуст** — Event Loop берёт следующую задачу из очереди.
Когда JS встречает асинхронную операцию, он передаёт её движку окружения:
setTimeout(fn, 1000) → Timer API → ждёт 1000ms → кладёт fn в Macrotask Queue
fetch(url) → Network API → ждёт ответ → кладёт колбэк в Microtask QueueJS-поток при этом **не блокируется** — он продолжает выполнять синхронный код.
┌─────────────────────────────────────────────────┐
│ Call Stack │
└─────────────────────────────────────────────────┘
↑ ↑
┌────────────────────┐ ┌───────────────────────┐
│ Microtask Queue │ │ Macrotask Queue │
│ (приоритет выше) │ │ (приоритет ниже) │
│ │ │ │
│ Promise.then() │ │ setTimeout() │
│ Promise.catch() │ │ setInterval() │
│ queueMicrotask() │ │ I/O callbacks │
│ MutationObserver │ │ requestAnimationFrame │
└────────────────────┘ └───────────────────────┘1. Выполнить весь синхронный код (пока стек не опустеет)
2. Выполнить ВСЕ микрозадачи из Microtask Queue
(если во время выполнения микрозадачи добавились новые — тоже выполнить)
3. Взять ОДНУ макрозадачу из Macrotask Queue и выполнить её
4. Снова выполнить ВСЕ микрозадачи
5. Повторять с шага 3**Ключевое правило**: микрозадачи выполняются **все** до следующей макрозадачи.
console.log('1 — sync')
setTimeout(() => console.log('2 — macrotask'), 0)
Promise.resolve()
.then(() => console.log('3 — microtask 1'))
.then(() => console.log('4 — microtask 2'))
console.log('5 — sync')
// Вывод:
// 1 — sync
// 5 — sync
// 3 — microtask 1
// 4 — microtask 2
// 2 — macrotaskОбъяснение:
1. console.log('1') — синхронно
2. setTimeout — колбэк уходит в Macrotask Queue (через 0ms)
3. Promise.resolve().then(...) — колбэк в Microtask Queue
4. console.log('5') — синхронно
5. Стек пуст → выполняем все микрозадачи: 3, затем 4
6. Берём макрозадачу из очереди: 2
// Даже если setTimeout задержка = 0мс,
// Promise.then выполнится раньше, потому что
// микрозадачи имеют более высокий приоритет
setTimeout(() => console.log('setTimeout 0ms'), 0)
Promise.resolve().then(() => console.log('Promise.then'))
// Promise.then
// setTimeout 0ms// ПЛОХО: бесконечный цикл микрозадач заблокирует макрозадачи
function infiniteMicrotasks() {
Promise.resolve().then(infiniteMicrotasks)
}
// Это заморозит браузер — setTimeout никогда не выполнится!Macrotask → Microtasks → rAF → Paint → Macrotask → ...requestAnimationFrame вызывается перед каждой перерисовкой (~60 раз в секунду). Используй его для анимаций вместо setTimeout.
**Начни со структуры**: "JavaScript однопоточный. Event Loop — механизм асинхронности. Есть Call Stack, Microtask Queue и Macrotask Queue."
**Объясни приоритеты**: "Микрозадачи всегда выполняются перед макрозадачами. Promise.then — микрозадача. setTimeout — макрозадача."
**Покажи пример**: запишите console.log + setTimeout(0) + Promise.then и объясни порядок вывода. Это классический вопрос.
**Время ответа**: 3-4 минуты. Нарисуй схему с очередями — это очень помогает.
1. **"setTimeout(fn, 0) выполняется сразу"** — нет. Он попадает в макрозадачу и выполнится только после всех синхронных операций и микрозадач.
2. **Путаница микрозадач и макрозадач** — если не знаешь разницу между Promise.then и setTimeout — это провал на большинстве JS-собеседований.
3. **"JavaScript многопоточный"** — JS однопоточный. Web Workers — отдельные потоки, но они не имеют доступа к DOM и общаются через сообщения.
Демонстрация порядка выполнения: синхронный код, микрозадачи (Promise), макрозадачи (setTimeout)
// Классический вопрос на собеседовании: предскажи порядок вывода
console.log('=== Старт ===')
// Макрозадача (низкий приоритет)
setTimeout(() => {
console.log('setTimeout 1 (макрозадача)')
// Микрозадача внутри макрозадачи
Promise.resolve().then(() => {
console.log('Promise внутри setTimeout (микрозадача)')
})
}, 0)
// Микрозадачи (высокий приоритет)
Promise.resolve()
.then(() => {
console.log('Promise.then #1 (микрозадача)')
return 'значение'
})
.then((val) => {
console.log('Promise.then #2 (микрозадача), получил:', val)
})
// Ещё одна микрозадача
queueMicrotask(() => {
console.log('queueMicrotask (микрозадача)')
})
// Ещё одна макрозадача
setTimeout(() => {
console.log('setTimeout 2 (макрозадача)')
}, 0)
console.log('=== Конец синхронного кода ===')
/*
Ожидаемый вывод:
=== Старт ===
=== Конец синхронного кода ===
Promise.then #1 (микрозадача)
queueMicrotask (микрозадача)
Promise.then #2 (микрозадача)
setTimeout 1 (макрозадача)
Promise внутри setTimeout (микрозадача)
setTimeout 2 (макрозадача)
Почему именно так?
1. Весь синхронный код выполнился: "Старт" и "Конец"
2. Все микрозадачи: Promise.then #1, queueMicrotask, Promise.then #2
(Promise.then #2 добавился в очередь когда выполнился #1)
3. Первая макрозадача: setTimeout 1
4. Микрозадачи после макрозадачи: Promise внутри setTimeout
5. Следующая макрозадача: setTimeout 2
*/
// ===== Визуализация Event Loop =====
console.log('\n=== Визуализация очередей ===')
function visualEventLoop() {
const callStack = []
const microtaskQueue = []
const macrotaskQueue = []
// Имитация состояния очередей
function logState(action) {
console.log(`\n[Action]: ${action}`)
console.log(` Call Stack: [${callStack.join(', ')}]`)
console.log(` Microtask Queue: [${microtaskQueue.join(', ')}]`)
console.log(` Macrotask Queue: [${macrotaskQueue.join(', ')}]`)
}
callStack.push('main()')
macrotaskQueue.push('setTimeout cb')
microtaskQueue.push('Promise.then')
logState('После синхронного кода')
callStack.pop()
logState('Стек пуст — берём микрозадачи')
const micro = microtaskQueue.shift()
callStack.push(micro)
logState(`Выполняем микрозадачу: ${micro}`)
callStack.pop()
logState('Все микрозадачи выполнены')
const macro = macrotaskQueue.shift()
callStack.push(macro)
logState(`Берём макрозадачу: ${macro}`)
callStack.pop()
logState('Цикл завершён')
}
visualEventLoop()JavaScript однопоточный — он выполняет только одну задачу за раз. Event Loop — это механизм, который позволяет JS быть асинхронным: пока выполняется синхронный код, асинхронные операции (таймеры, fetch) обрабатываются браузером/Node.js в фоне, а их колбэки ставятся в очередь. Event Loop следит за тем, чтобы очередь задач выполнялась, когда стек вызовов пуст. Ключевое: микрозадачи (Promise.then) всегда выполняются перед макрозадачами (setTimeout).
Работает по принципу LIFO (Last In, First Out). Каждый вызов функции добавляет **фрейм** в стек. Когда функция завершается — фрейм удаляется.
function c() { /* ... */ }
function b() { c() }
function a() { b() }
a()
Стек (растёт вниз):
│ c() │ ← выполняется сейчас
│ b() │
│ a() │
│ main │
└──────┘Когда стек **пуст** — Event Loop берёт следующую задачу из очереди.
Когда JS встречает асинхронную операцию, он передаёт её движку окружения:
setTimeout(fn, 1000) → Timer API → ждёт 1000ms → кладёт fn в Macrotask Queue
fetch(url) → Network API → ждёт ответ → кладёт колбэк в Microtask QueueJS-поток при этом **не блокируется** — он продолжает выполнять синхронный код.
┌─────────────────────────────────────────────────┐
│ Call Stack │
└─────────────────────────────────────────────────┘
↑ ↑
┌────────────────────┐ ┌───────────────────────┐
│ Microtask Queue │ │ Macrotask Queue │
│ (приоритет выше) │ │ (приоритет ниже) │
│ │ │ │
│ Promise.then() │ │ setTimeout() │
│ Promise.catch() │ │ setInterval() │
│ queueMicrotask() │ │ I/O callbacks │
│ MutationObserver │ │ requestAnimationFrame │
└────────────────────┘ └───────────────────────┘1. Выполнить весь синхронный код (пока стек не опустеет)
2. Выполнить ВСЕ микрозадачи из Microtask Queue
(если во время выполнения микрозадачи добавились новые — тоже выполнить)
3. Взять ОДНУ макрозадачу из Macrotask Queue и выполнить её
4. Снова выполнить ВСЕ микрозадачи
5. Повторять с шага 3**Ключевое правило**: микрозадачи выполняются **все** до следующей макрозадачи.
console.log('1 — sync')
setTimeout(() => console.log('2 — macrotask'), 0)
Promise.resolve()
.then(() => console.log('3 — microtask 1'))
.then(() => console.log('4 — microtask 2'))
console.log('5 — sync')
// Вывод:
// 1 — sync
// 5 — sync
// 3 — microtask 1
// 4 — microtask 2
// 2 — macrotaskОбъяснение:
1. console.log('1') — синхронно
2. setTimeout — колбэк уходит в Macrotask Queue (через 0ms)
3. Promise.resolve().then(...) — колбэк в Microtask Queue
4. console.log('5') — синхронно
5. Стек пуст → выполняем все микрозадачи: 3, затем 4
6. Берём макрозадачу из очереди: 2
// Даже если setTimeout задержка = 0мс,
// Promise.then выполнится раньше, потому что
// микрозадачи имеют более высокий приоритет
setTimeout(() => console.log('setTimeout 0ms'), 0)
Promise.resolve().then(() => console.log('Promise.then'))
// Promise.then
// setTimeout 0ms// ПЛОХО: бесконечный цикл микрозадач заблокирует макрозадачи
function infiniteMicrotasks() {
Promise.resolve().then(infiniteMicrotasks)
}
// Это заморозит браузер — setTimeout никогда не выполнится!Macrotask → Microtasks → rAF → Paint → Macrotask → ...requestAnimationFrame вызывается перед каждой перерисовкой (~60 раз в секунду). Используй его для анимаций вместо setTimeout.
**Начни со структуры**: "JavaScript однопоточный. Event Loop — механизм асинхронности. Есть Call Stack, Microtask Queue и Macrotask Queue."
**Объясни приоритеты**: "Микрозадачи всегда выполняются перед макрозадачами. Promise.then — микрозадача. setTimeout — макрозадача."
**Покажи пример**: запишите console.log + setTimeout(0) + Promise.then и объясни порядок вывода. Это классический вопрос.
**Время ответа**: 3-4 минуты. Нарисуй схему с очередями — это очень помогает.
1. **"setTimeout(fn, 0) выполняется сразу"** — нет. Он попадает в макрозадачу и выполнится только после всех синхронных операций и микрозадач.
2. **Путаница микрозадач и макрозадач** — если не знаешь разницу между Promise.then и setTimeout — это провал на большинстве JS-собеседований.
3. **"JavaScript многопоточный"** — JS однопоточный. Web Workers — отдельные потоки, но они не имеют доступа к DOM и общаются через сообщения.
Демонстрация порядка выполнения: синхронный код, микрозадачи (Promise), макрозадачи (setTimeout)
// Классический вопрос на собеседовании: предскажи порядок вывода
console.log('=== Старт ===')
// Макрозадача (низкий приоритет)
setTimeout(() => {
console.log('setTimeout 1 (макрозадача)')
// Микрозадача внутри макрозадачи
Promise.resolve().then(() => {
console.log('Promise внутри setTimeout (микрозадача)')
})
}, 0)
// Микрозадачи (высокий приоритет)
Promise.resolve()
.then(() => {
console.log('Promise.then #1 (микрозадача)')
return 'значение'
})
.then((val) => {
console.log('Promise.then #2 (микрозадача), получил:', val)
})
// Ещё одна микрозадача
queueMicrotask(() => {
console.log('queueMicrotask (микрозадача)')
})
// Ещё одна макрозадача
setTimeout(() => {
console.log('setTimeout 2 (макрозадача)')
}, 0)
console.log('=== Конец синхронного кода ===')
/*
Ожидаемый вывод:
=== Старт ===
=== Конец синхронного кода ===
Promise.then #1 (микрозадача)
queueMicrotask (микрозадача)
Promise.then #2 (микрозадача)
setTimeout 1 (макрозадача)
Promise внутри setTimeout (микрозадача)
setTimeout 2 (макрозадача)
Почему именно так?
1. Весь синхронный код выполнился: "Старт" и "Конец"
2. Все микрозадачи: Promise.then #1, queueMicrotask, Promise.then #2
(Promise.then #2 добавился в очередь когда выполнился #1)
3. Первая макрозадача: setTimeout 1
4. Микрозадачи после макрозадачи: Promise внутри setTimeout
5. Следующая макрозадача: setTimeout 2
*/
// ===== Визуализация Event Loop =====
console.log('\n=== Визуализация очередей ===')
function visualEventLoop() {
const callStack = []
const microtaskQueue = []
const macrotaskQueue = []
// Имитация состояния очередей
function logState(action) {
console.log(`\n[Action]: ${action}`)
console.log(` Call Stack: [${callStack.join(', ')}]`)
console.log(` Microtask Queue: [${microtaskQueue.join(', ')}]`)
console.log(` Macrotask Queue: [${macrotaskQueue.join(', ')}]`)
}
callStack.push('main()')
macrotaskQueue.push('setTimeout cb')
microtaskQueue.push('Promise.then')
logState('После синхронного кода')
callStack.pop()
logState('Стек пуст — берём микрозадачи')
const micro = microtaskQueue.shift()
callStack.push(micro)
logState(`Выполняем микрозадачу: ${micro}`)
callStack.pop()
logState('Все микрозадачи выполнены')
const macro = macrotaskQueue.shift()
callStack.push(macro)
logState(`Берём макрозадачу: ${macro}`)
callStack.pop()
logState('Цикл завершён')
}
visualEventLoop()Предскажи и объясни порядок вывода console.log для следующего кода. Реализуй функцию predictOutput(steps), которая принимает массив шагов с типами (sync/microtask/macrotask) и возвращает их в правильном порядке выполнения.
Порядок вывода ACGEBFD. Функция predictOutput: [...sync, ...microtasks, ...macrotasks].map(s => s.label). Помни: C добавляет новую макрозадачу D, поэтому D идёт после F.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке