В чем разница между способами объявления переменной?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Расскажи про Event Loop в JavaScript
Event Loop — это механизм, который делает JavaScript асинхронным несмотря на то, что язык однопоточный. Это один из самых сложных и важных концептов в JavaScript.
Однопоточность JavaScript
Проблема
JavaScript выполняет код в одном потоке (main thread). Это значит, что в одный момент времени выполняется только одна операция.
// Все это выполняется в одном потоке
console.log('1');
console.log('2');
console.log('3');
// Вывод: 1, 2, 3 (по порядку)
Но если операция заблокирует поток, все остановится:
// Плохо: блокирует поток
function blockThread() {
const start = Date.now();
while (Date.now() - start < 5000) {
// 5 секунд ничего не делает
}
}
console.log('Старт');
blockThread(); // Поток заблокирован на 5 секунд!
console.log('Конец'); // Выведется только через 5 секунд
Как работает Event Loop
Три основных компонента
- Call Stack — стек вызовов функций
- Web APIs — API браузера (setTimeout, fetch, DOM)
- Callback Queue (Macrotask/Microtask Queue) — очередь обратных вызовов
Процесс
┌─────────────────────┐
│ Call Stack │ <- Текущий код
└─────────────────────┘
↓ (когда пусто)
┌─────────────────────┐
│ Microtask Queue │ <- Promises, queueMicrotask
└─────────────────────┘
↓ (когда пусто)
┌─────────────────────┐
│ Macrotask Queue │ <- setTimeout, setInterval, I/O
└─────────────────────┘
Алгоритм Event Loop
1. Выполнить весь код из Call Stack
2. Когда Call Stack пуст:
a. Выполнить все задачи из Microtask Queue
b. Перерисовать (repaint) страницу
c. Выполнить одну задачу из Macrotask Queue
3. Вернуться на шаг 2
Пример: разные типы очередей
console.log('Начало'); // Call Stack
setTimeout(() => {
console.log('setTimeout'); // Macrotask Queue
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise 1'); // Microtask Queue
})
.then(() => {
console.log('Promise 2'); // Microtask Queue
});
console.log('Конец'); // Call Stack
// Вывод:
// Начало
// Конец
// Promise 1
// Promise 2
// setTimeout
Объяснение порядка
- 'Начало' и 'Конец' — синхронный код из Call Stack
- Promise 1 и 2 — из Microtask Queue (выполняются раньше setTimeout)
- setTimeout — из Macrotask Queue (выполняется последним)
Call Stack
Как работает стек
function greet(name) {
const greeting = `Hello, ${name}`;
return greeting;
}
function sayHello() {
const message = greet('Alice');
console.log(message);
}
sayHello();
// Call Stack во время выполнения:
// sayHello()
// greet('Alice')
// return greeting
// console.log(message)
// (пусто)
Microtask Queue
Что туда попадает
// 1. Promises
Promise.resolve().then(() => console.log('Promise'));
// 2. queueMicrotask
queueMicrotask(() => console.log('Microtask'));
// 3. MutationObserver
const observer = new MutationObserver(() => console.log('DOM changed'));
observer.observe(document.body, { childList: true });
// 4. async/await (это синтаксический сахар для Promises)
async function test() {
await fetch('/api'); // Microtask
}
Важно: Microtask выполняются раньше repaint
const box = document.getElementById('box');
// Изменяем стиль
box.style.backgroundColor = 'red';
// Проверяем вычисленный стиль
Promise.resolve().then(() => {
console.log(getComputedStyle(box).backgroundColor);
// 'rgb(255, 0, 0)' или 'red' — уже красный!
});
// setTimeout исполняется после repaint
setTimeout(() => {
console.log('После repaint');
}, 0);
// Вывод:
// red (microtask видит изменение)
// После repaint (macrotask выполнится позже)
Macrotask Queue
Что туда попадает
// 1. setTimeout / setInterval
setTimeout(() => console.log('Macrotask 1'), 0);
setInterval(() => console.log('Macrotask 2'), 1000);
// 2. setImmediate (Node.js, не во всех браузерах)
setImmediate(() => console.log('Immediate'));
// 3. I/O операции
fetch('/api').then(data => console.log('I/O'));
// 4. UI события
element.addEventListener('click', () => console.log('Click'));
// 5. requestAnimationFrame (выполняется перед repaint, но после microtasks)
requestAnimationFrame(() => console.log('Animation'));
Сложный пример
console.log('1: Start');
setTimeout(() => {
console.log('2: setTimeout callback');
Promise.resolve().then(() => console.log('3: Promise in setTimeout'));
}, 0);
Promise.resolve()
.then(() => {
console.log('4: Promise 1');
setTimeout(() => console.log('5: setTimeout in Promise'), 0);
})
.then(() => {
console.log('6: Promise 2');
});
console.log('7: End');
// Вывод:
// 1: Start
// 7: End
// 4: Promise 1
// 6: Promise 2
// 2: setTimeout callback
// 3: Promise in setTimeout
// 5: setTimeout in Promise
Объяснение
1. Синхронный код: '1' и '7'
2. Microtask Queue: '4', '6' (обе части Promise chain)
3. Первая macrotask: '2' + её microtasks: '3'
4. Вторая macrotask: '5'
requestAnimationFrame
Особенность: выполняется ПЕРЕД repaint
console.log('Start');
requestAnimationFrame(() => {
console.log('RAF');
});
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
// Вывод:
// Start
// End
// Promise (microtask)
// RAF (перед repaint)
// setTimeout (macrotask)
Практические примеры
Проблема: Infinite loop в Event Loop
// ПЛОХО: бесконечно добавляет обработчик
Promise.resolve()
.then(() => {
console.log('Привет');
Promise.resolve().then(...); // Еще микротаск!
});
// Это создаст бесконечный цикл микротасков!
Хорошо: есть условие выхода
let count = 0;
function processQueue() {
if (count++ > 10) return;
Promise.resolve().then(() => {
console.log('Обработка ' + count);
processQueue();
});
}
processQueue();
Заключение
Event Loop — это сердце асинхронности в JavaScript. Понимание порядка выполнения (синхронный код → microtasks → macrotasks) критично для:
- Избежания race conditions
- Оптимизации производительности
- Понимания async/await
- Отладки timing-зависимых проблем
Запомни: Microtask всегда быстрее macrotask, даже если setTimeout с 0 миллисекунд.