Моделът за изпълнение на JavaScript е нюансиран и лесен за погрешно разбиране. Научаването за цикъла на събитията в основата му може да помогне.

JavaScript е еднонишков език, създаден да обработва задачи една по една. Цикълът на събитията обаче позволява на JavaScript да обработва събития и обратни извиквания асинхронно чрез емулиране на системи за едновременно програмиране. Това гарантира производителността на вашите JavaScript приложения.

Какво представлява JavaScript цикълът на събитията?

Цикълът за събития на JavaScript е механизъм, който работи във фонов режим на всяко JavaScript приложение. Той позволява на JavaScript да обработва задачи последователно, без да блокира основната му нишка за изпълнение. Това се нарича асинхронно програмиране.

Цикълът на събитията поддържа опашка от задачи за изпълнение и подава тези задачи вдясно уеб API за изпълнение един по един. JavaScript следи тези задачи и обработва всяка според нивото на сложност на задачата.

Да разбере необходимостта от цикъл на събития на JavaScript и асинхронно програмиране. Трябва да разберете какъв проблем по същество решава.

instagram viewer

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

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();

Този код първо дефинира функция, наречена longRunningFunction(). Тази функция ще изпълни някакъв вид сложна задача, отнемаща време. В този случай той извършва a за цикъл, повтарящ се над 100 000 пъти. Това означава, че console.log("Здравей") работи 100 000 пъти.

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

За контекст, ето сравнение на времето, необходимо за изпълнение на двете функции:

И след това сингъла shortRunningFunction():

Разликата между операция от 2351 милисекунди и операция от 0 милисекунди е очевидна, когато се стремите да създадете ефективно приложение.

Как Event Loop помага за производителността на приложението

Цикълът на събитията има различни етапи и части, които допринасят за работата на системата.

Стекът за повиквания

Стекът за извикване на JavaScript е от съществено значение за това как JavaScript обработва извиквания на функции и събития от вашето приложение. JavaScript кодът се компилира отгоре надолу. Въпреки това, Node.js, при четене на кода, Node.js ще присвои извиквания на функции отдолу нагоре. Докато чете, той избутва дефинираните функции като рамки в стека за повиквания една по една.

Стекът за извикване отговаря за поддържането на контекста на изпълнение и правилния ред на функциите. Той прави това, като работи като стек Последен влязъл, първи излязъл (LIFO).

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

JavaScript ще извади всеки кадър от стека, докато не се изпразни, което означава, че всички функции са приключили работата си.

Libuv Web API

В основата на асинхронните програми на JavaScript е libuv. Библиотеката 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. Ако в опашката има обратно извикване, то избутва първото в стека за извикване и го изпълнява.

По този начин асинхронните задачи не блокират основната нишка и опашката за обратно извикване гарантира, че съответните им обратни извиквания се изпълняват в реда, в който са завършили.

Цикълът на цикъла на събитията

Цикълът на събития също има нещо, наречено опашка с микрозадачи. Тази специална опашка в цикъла на събитията съдържа микрозадачи, планирани да се изпълнят веднага щом текущата задача в стека за повиквания завърши. Това изпълнение се случва преди следващото изобразяване или итерация на цикъл на събитие. Микрозадачите са задачи с висок приоритет с предимство пред обикновените задачи в цикъла на събитията.

Микрозадача обикновено се създава при работа с Promises. Всеки път, когато дадено обещание се разреши или отхвърли, това съответства .тогава() или .catch() 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);
});
});

В този пример всяко щракване върху бутона „Извличане“ извиква fetchData(). Всяка операция за извличане на данни се планира като микрозадача. Въз основа на извлечените данни актуализацията на потребителския интерфейс се изпълнява веднага след приключване на всяка операция за извличане, преди всички други задачи за изобразяване или цикъл на събития.

Това гарантира, че потребителите виждат актуализираните данни, без да изпитват забавяния поради други задачи в цикъла на събитията.

Използването на микрозадачи в сценарии като този може да предотврати UI bank и да осигури по-бързи и плавни взаимодействия във вашето приложение.

Последици от цикъла на събитията за уеб разработката

Разбирането на цикъла на събитията и как да използвате неговите функции е от съществено значение за изграждането на ефективни и отзивчиви приложения. Цикълът на събитията предоставя асинхронни и паралелни възможности, така че можете ефективно да се справяте със сложни задачи във вашето приложение, без да компрометирате потребителското изживяване.

Node.js предоставя всичко необходимо, включително уеб работници за постигане на допълнителен паралелизъм извън основната нишка на JavaScript.