Модель выполнения JavaScript имеет множество нюансов, и ее легко понять неправильно. Изучение цикла событий в его основе может помочь.

JavaScript — это однопоточный язык, созданный для обработки задач по одной за раз. Однако цикл событий позволяет JavaScript обрабатывать события и обратные вызовы асинхронно, эмулируя системы одновременного программирования. Это гарантирует производительность ваших приложений JavaScript.

Что такое цикл событий JavaScript?

Цикл событий JavaScript — это механизм, который работает в фоновом режиме каждого приложения JavaScript. Это позволяет JavaScript обрабатывать задачи последовательно, не блокируя основной поток выполнения. Это называется асинхронное программирование.

Цикл событий поддерживает очередь задач для запуска и подает эти задачи справа веб-API для выполнения по одному. JavaScript отслеживает эти задачи и обрабатывает каждую в соответствии с уровнем сложности задачи.

Чтобы понять необходимость цикла обработки событий JavaScript и асинхронного программирования. Нужно понимать, какую проблему он по существу решает.

Возьмите этот код, например:

functionlongRunningFunction() {
// This function does something that takes a long time to execute.
for (var i = 0; i < 100000; i++) {
console.log("Hello")
}
}

functionshortRunningFunction(a) {
return a * 2 ;
}

functionmain() {
var startTime = Date.now();
longRunningFunction();

var endTime = Date.now();

// Prints the amount of time it took to execute functions
console.log(shortRunningFunction(2));
console.log("Time taken: " + (endTime - startTime) + " milliseconds");
}

main();

Этот код сначала определяет функцию с именем функция longRunning(). Эта функция выполнит какую-то сложную трудоемкую задачу. В этом случае он выполняет для цикл повторяется более 100 000 раз. Это значит, что console.log("Привет") проходит 100 000 раз.

В зависимости от скорости компьютера это может занять много времени и заблокировать функция короткого выполнения () от немедленного выполнения до завершения предыдущей функции.

Для контекста, вот сравнение времени, затрачиваемого на выполнение обеих функций:

А потом сингл функция короткого выполнения ():

Разница между операцией за 2351 миллисекунду и операцией за 0 миллисекунд очевидна, если вы хотите создать производительное приложение.

Как цикл событий помогает повысить производительность приложения

Цикл событий имеет различные этапы и части, которые способствуют работе системы.

Стек вызовов

Стек вызовов JavaScript необходим для того, как JavaScript обрабатывает вызовы функций и событий из вашего приложения. Код JavaScript компилируется сверху вниз. Однако Node.js при чтении кода Node.js будет назначать вызовы функций снизу вверх. По мере чтения он помещает определенные функции в виде фреймов в стек вызовов один за другим.

Стек вызовов отвечает за поддержание контекста выполнения и правильного порядка функций. Он делает это, работая как стек «последним пришел — первым обслужен» (LIFO).

Это означает, что последний кадр функции, который ваша программа поместит в стек вызовов, будет первым, который выскочит из стека и запустится. Это гарантирует, что JavaScript поддерживает правильный порядок выполнения функций.

JavaScript будет выталкивать каждый кадр из стека до тех пор, пока он не станет пустым, что означает завершение выполнения всех функций.

Библиотечный веб-API

В основе асинхронных программ JavaScript лежит либув. Библиотека libuv написана на языке программирования C, который может взаимодействовать с операционной системой. низкоуровневые API. Библиотека будет предоставлять несколько API, позволяющих выполнять код JavaScript параллельно с другими. код. API для создания потоков, API для связи между потоками и API для управления синхронизацией потоков.

Например, когда вы используете setTimeout в Node.js, чтобы приостановить выполнение. Таймер настраивается через libuv, который управляет циклом обработки событий для выполнения функции обратного вызова по истечении указанной задержки.

Точно так же, когда вы выполняете сетевые операции асинхронно, libuv обрабатывает эти операции в неблокирующем режиме. образом, гарантируя, что другие задачи могут продолжать обработку, не дожидаясь завершения операции ввода/вывода (I/O). конец.

Обратный вызов и очередь событий

Очередь обратного вызова и событий — это место, где функции обратного вызова ожидают выполнения. Когда асинхронная операция завершается из libuv, соответствующая функция обратного вызова добавляется в эту очередь.

Вот как проходит последовательность:

  1. JavaScript перемещает асинхронные задачи в libuv для обработки и продолжает немедленно обрабатывать следующую задачу.
  2. Когда асинхронная задача завершается, JavaScript добавляет свою функцию обратного вызова в очередь обратного вызова.
  3. JavaScript продолжает выполнять другие задачи в стеке вызовов, пока не закончит все в текущем порядке.
  4. Когда стек вызовов пуст, JavaScript просматривает очередь обратного вызова.
  5. Если в очереди есть обратный вызов, он помещает первый в стек вызовов и выполняет его.

Таким образом, асинхронные задачи не блокируют основной поток, а очередь обратного вызова гарантирует, что соответствующие им обратные вызовы выполняются в том порядке, в котором они были завершены.

Цикл цикла событий

Цикл событий также имеет то, что называется очередью микрозадач. Эта специальная очередь в цикле событий содержит микрозадачи, выполнение которых запланировано, как только завершится текущая задача в стеке вызовов. Это выполнение происходит перед следующей итерацией рендеринга или цикла обработки событий. Микрозадачи — это задачи с высоким приоритетом, имеющие приоритет над обычными задачами в цикле событий.

Микрозадача обычно создается при работе с промисами. Всякий раз, когда промис разрешается или отклоняется, соответствующий .затем() или .ловить() callbacks присоединяется к очереди микрозадач. Вы можете использовать эту очередь для управления задачами, требующими немедленного выполнения после текущей операции, такими как обновление пользовательского интерфейса вашего приложения или обработка изменений состояния.

Например, веб-приложение, которое выполняет выборку данных и обновляет пользовательский интерфейс на основе полученных данных. Пользователи могут инициировать эту выборку данных, многократно нажимая кнопку. Каждое нажатие кнопки инициирует операцию асинхронного извлечения данных.

Без микрозадач цикл событий для этой задачи работал бы следующим образом:

  1. Пользователь несколько раз нажимает кнопку.
  2. Каждое нажатие кнопки запускает операцию асинхронной выборки данных.
  3. По завершении операций выборки данных JavaScript добавляет соответствующие обратные вызовы в обычную очередь задач.
  4. Цикл событий начинает обработку задач в обычной очереди задач.
  5. Обновление пользовательского интерфейса на основе результатов выборки данных выполняется, как только это позволяют обычные задачи.

Однако с микрозадачами цикл событий работает иначе:

  1. Пользователь несколько раз нажимает кнопку и запускает операцию асинхронной выборки данных.
  2. По завершении операций выборки данных цикл обработки событий добавляет соответствующие обратные вызовы в очередь микрозадач.
  3. Цикл событий начинает обработку задач в очереди микрозадач сразу после завершения текущей задачи (нажатие кнопки).
  4. Обновление пользовательского интерфейса на основе результатов выборки данных выполняется перед следующей обычной задачей, обеспечивая более быстрое взаимодействие с пользователем.

Вот пример кода:

const fetchData = () => {
returnnewPromise(resolve => {
setTimeout(() => resolve('Data from fetch'), 2000);
});
};

document.getElementById('fetch-button').addEventListener('click', () => {
fetchData().then(data => {
// This UI update will run before the next rendering cycle
updateUI(data);
});
});

В этом примере каждый щелчок по кнопке «Выбрать» вызывает выборка данных(). Каждая операция выборки данных планируется как микрозадача. На основе извлеченных данных обновление пользовательского интерфейса выполняется сразу после завершения каждой операции выборки, до любых других задач рендеринга или цикла событий.

Это гарантирует, что пользователи увидят обновленные данные без каких-либо задержек из-за других задач в цикле обработки событий.

Использование микрозадач в таких сценариях может предотвратить перегрузку пользовательского интерфейса и обеспечить более быстрое и плавное взаимодействие в вашем приложении.

Последствия цикла событий для веб-разработки

Понимание цикла событий и того, как использовать его функции, необходимо для создания производительных и отзывчивых приложений. Цикл событий обеспечивает асинхронные и параллельные возможности, поэтому вы можете эффективно решать сложные задачи в своем приложении, не ставя под угрозу взаимодействие с пользователем.

Node.js предоставляет все необходимое, в том числе веб-воркеры для дальнейшего параллелизма за пределами основного потока JavaScript.