Event Loop в JS
Компоненты, выполняющие код в браузере:
- Call Stack – это стек вызовов, где выполняется JavaScript-код. Это часть движка JavaScript (например, V8 в Chrome).
- Event Loop – механизм, не являющийся частью движка JavaScript. Предоставляется браузером или средой выполнения Node.js.
- Взаимодействие через Web API – Call Stack взаимодействует с Event Loop через Web API, такие как
setTimeout
,addEventListener
,fetch
и другие. - Есть 2 очереди задач (Task Queue) для асинхронных операций:
- Microtasks включают операции с промисами (например,
fetch
),queueMicrotask
,MutationObserver
. - Macrotasks включают
setTimeout
,setInterval
, события вроде кликов, задачи, связанные с рендерингом страницы.
- Microtasks включают операции с промисами (например,
Очередность обработки кода в JS
- Выполнение начинается с синхронных задач из Call Stack по очереди одна за другой.
- Затем Event Loop обрабатывает все задачи из Microtask Queue. (это значит, что каждая задача последовательно перемещается в Call Stack и выполняется)
- Далее обрабатывается одна задача из Macrotask Queue.
- Цикл повторяется с первого шага.
Обработка setTimeout
Вызов setTimeout(function(){console.log('hello')}, 3000)
отправляет функцию в Web API, где инициируется таймер. По истечении таймера функция перемещается в Task Queue, а именно в Mactotasks.
Как только Call Stack опустеет, будут выполняться все задачи из microtask queue (если они имеются. но данном случае их нет), а потом наша функция из Task Queue перемещается в Call Stack для выполнения и выполяется.
Event Loop обеспечивает асинхронное выполнение задач, позволяя JavaScript оставаться однопоточным, но поддерживать обработку событий, Input/Output операций и таймеров без блокирования главного потока.
Однако важно помнить, что! JS – выполняется в одном потоке!
Тяжелой микротаской или макротаской (они все равно в конечном итоге выполняются в основном потоке) можно все равно заставить тормозить браузер!!!
Не занимать основной поток более чем на 50 мс (в идеале)
Нужно давать коду “дышать”. Нужно избегать выполнения длительных операций без перерыва. Для поддержания отзывчивости интерфейса главный поток не должен быть занят более 50 миллисекунд за раз. Вообще, при частоте 60 кадров в секунду (возьмем обычный дисплей) каждый кадр должен генерироваться примерно каждые 16,67 миллисекунд. Тем не менее, реальность выполнения кода в браузере такова, что между кадрами могут возникать задержки из-за работы механизмов рендеринга и других системных задач. Поэтому для выполнения JavaScript-кода часто рекомендуется оставлять более широкий “бюджет” времени — примерно до 50 миллисекунд.
Всегда надо помнить, что событие клика мышкой, отрисовка интерфейса обрабатывается макротаской. И нужно чтобы она успевала бы обрабатываться, чтобы интерфейс у пользователя не тормозил. Поэтому большие по времени задачи – необходимо разбивать на части.
Итак, как избежать блокировки главного потока
- Разделение больших задач.
Для избежания блокировки главного потока важно разделять большие и тяжёлые задачи на меньшие части. Нужно использовать асинхронные функции, такие какsetTimeout
илиPromise
, для отложенного выполнения частей кода, что позволяет браузеру обрабатывать другие события. - Использование Web Workers.
При выполнении действительно тяжёлых вычислительных операций следует рассмотреть возможность использования Web Workers. Это позволяет выполнять код JavaScript в фоновом потоке, не влияя на производительность главного потока. - Оптимизация циклов и обработчиков событий.
Необходимо обеспечить максимальную эффективность циклов и обработчиков событий. Следует избегать сложных операций внутри функций, часто вызываемых при событиях, таких какscroll
илиresize
. - Применение практик асинхронности.
Важно использовать асинхронные API, такие какfetch
для сетевых запросов, и промисы для обработки результатов. Это помогает уменьшить время, в течение которого главный поток остаётся неактивным, улучшая отзывчивость приложения. - Приоритизация задач.
Необходимо использовать микрозадачи для обработки критически важных обновлений и макрозадачи для менее срочных задач. Важно помнить, что Event Loop сначала обрабатывает все микрозадачи, прежде чем переходить к макрозадачам.
Что выведет код?
console.log(1);
setTimeout(() => {
console.log("settimeout1");
}, 0);
console.log(2);
Promise.resolve().then(() => {
console.log("promise 1");
});
setTimeout(() => {
setTimeout(() => {
console.log("settimeout form settimeout");
}, 0);
Promise.resolve().then(() => {
console.log("promise 1 settimeout 1");
});
Promise.resolve().then(() => {
console.log("promise 2 settimeout 1");
});
console.log("settimeout 2");
}, 0);
Promise.resolve().then(() => {
console.log("promise 2");
});
console.log(3);
Ответ. (кликните. но сначала напишите сами)
12
3
promise 1
promise 2
set timeout1
settimeout 2
promise 1 settimeout 1
promise 2 settimeout 1