← Назад к вопросам

Как происходит обработка очереди событий?

2.0 Middle🔥 181 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обработка очереди событий в JavaScript

Очередь событий (Event Queue) - это одна из ключевых концепций в JavaScript, которая объясняет асинхронное выполнение кода. Давайте разберемся, как это работает.

Архитектура Event Loop

JavaScript работает на основе Event Loop - это механизм, который постоянно проверяет, есть ли задачи для выполнения.

// Визуализация работы Event Loop:
// 1. Call Stack - стек вызовов функций
// 2. Web APIs - браузерные API (setTimeout, fetch, addEventListener)
// 3. Callback Queue - очередь обратных вызовов
// 4. Event Loop - проверяет когда stack пуст

console.log("1: Начало");

setTimeout(() => {
  console.log("2: setTimeout (переходит в callback queue)");
}, 0);

Promise.resolve().then(() => {
  console.log("3: Promise (микротаск)");
});

console.log("4: Конец");

// Вывод:
// 1: Начало
// 4: Конец
// 3: Promise (микротаск выполняется раньше макротаска)
// 2: setTimeout

Call Stack (стек вызовов)

Это место, где выполняется синхронный код. JavaScript обрабатывает функции по принципу LIFO (Last In, First Out).

function functionA() {
  functionB();
  console.log("A finished");
}

function functionB() {
  console.log("B executing");
}

functionA();

// Стек:
// 1. functionA() добавляется в стек
// 2. functionB() добавляется в стек (сверху)
// 3. functionB() выполняется и удаляется
// 4. functionA() продолжает и удаляется

Макротаски и Микротаски

Важное различие - есть две очереди: макротаски и микротаски.

console.log("Start");

// Микротаска 1 - Promise
Promise.resolve().then(() => {
  console.log("Микротаска 1");
});

// Макротаска 1 - setTimeout
setTimeout(() => {
  console.log("Макротаска 1");
  
  // Микротаска 2 - Promise внутри макротаски
  Promise.resolve().then(() => {
    console.log("Микротаска 2");
  });
}, 0);

// Микротаска 3 - Promise
Promise.resolve().then(() => {
  console.log("Микротаска 3");
});

console.log("End");

// Вывод:
// Start
// End
// Микротаска 1
// Микротаска 3
// Макротаска 1
// Микротаска 2

Типы макротаск

// Макротаски:
setTimeout(() => {}, 0);
setInterval(() => {}, 0);
requestAnimationFrame(() => {});
element.addEventListener("click", () => {});

Типы микротаск

// Микротаски:
Promise.resolve().then(() => {});
queueMicrotask(() => {});
MutationObserver

Полный цикл Event Loop

// 1. Выполнить весь синхронный код
console.log("1. Синхронный код");

// 2. Проверить микротаски и выполнить все
Promise.resolve().then(() => {
  console.log("2. Микротаски");
});

// 3. Выполнить одну макротаску
setTimeout(() => {
  console.log("3. Макротаска");
  
  // 4. После макротаски снова проверить микротаски
  Promise.resolve().then(() => {
    console.log("4. Микротаски после макротаски");
  });
}, 0);

Практический пример: оптимизация

// Неправильно - микротаски могут блокировать UI
for (let i = 0; i < 1000000; i++) {
  Promise.resolve().then(() => {
    // Тяжелое вычисление
  });
}

// Правильно - использовать макротаски для разделения нагрузки
function processLargeData() {
  for (let i = 0; i < 100; i++) {
    setTimeout(() => {
      // Обработать 100 элементов
    }, 0);
  }
}

Важные выводы

  1. JavaScript - однопоточный язык, но Event Loop делает его асинхронным
  2. Микротаски выполняются ПЕРЕД следующей макротаской
  3. После макротаски браузер проверяет микротаски ДО следующей макротаски
  4. Рендеринг происходит между макротасками (через requestAnimationFrame)
  5. Понимание Event Loop критично для оптимизации производительности