Modelul de execuție JavaScript este nuanțat și ușor de înțeles greșit. Învățarea despre bucla de eveniment în centrul său poate ajuta.
JavaScript este un limbaj cu un singur thread, creat pentru a gestiona sarcini pe rând. Cu toate acestea, bucla de evenimente permite JavaScript să gestioneze evenimentele și apelurile inverse în mod asincron prin emularea sistemelor de programare simultană. Acest lucru asigură performanța aplicațiilor dvs. JavaScript.
Ce este bucla de evenimente JavaScript?
Bucla de evenimente JavaScript este un mecanism care rulează în fundal al fiecărei aplicații JavaScript. Permite JavaScript să gestioneze sarcini în secvență fără a bloca firul de execuție principal. Aceasta este denumită programare asincronă.
Bucla de evenimente păstrează o coadă de sarcini de rulat și alimentează acele sarcini în dreapta API web pentru executare pe rând. JavaScript ține evidența acestor sarcini și se ocupă de fiecare în funcție de nivelul de complexitate al sarcinii.
Pentru a înțelege necesitatea buclei de evenimente JavaScript și a programării asincrone. Trebuie să înțelegeți ce problemă rezolvă în esență.
Luați acest cod, de exemplu:
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();
Acest cod definește mai întâi o funcție numită longRunningFunction(). Această funcție va realiza un fel de sarcină complexă, care necesită timp. În acest caz, efectuează a pentru repetarea buclei de peste 100.000 de ori. Aceasta înseamnă că console.log("Bună ziua") rulează de 100.000 de ori.
În funcție de viteza computerului, acest lucru poate dura mult timp și poate bloca shortRunningFunction() de la execuția imediată până la finalizarea funcției anterioare.
Pentru context, iată o comparație a timpului necesar rulării ambelor funcții:
Și apoi single-ul shortRunningFunction():
Diferența dintre o operațiune de 2.351 de milisecunde și o operație de 0 milisecunde este evidentă atunci când doriți să construiți o aplicație performantă.
Cum ajută bucla de evenimente la performanța aplicației
Bucla de evenimente are diferite etape și părți care contribuie la funcționarea sistemului.
Stiva de apeluri
Stiva de apeluri JavaScript este esențială pentru modul în care JavaScript gestionează apelurile de funcții și evenimente din aplicația dvs. Codul JavaScript se compilează de sus în jos. Cu toate acestea, Node.js, la citirea codului, Node.js va atribui apeluri de funcții de jos în sus. Pe măsură ce citește, împinge funcțiile definite ca cadre în stiva de apeluri unul câte unul.
Stiva de apeluri este responsabilă pentru menținerea contextului de execuție și a ordinii corecte a funcțiilor. Face acest lucru funcționând ca o stivă Last-In-First-Out (LIFO).
Aceasta înseamnă că ultimul cadru de funcție pe care programul dvs. îl împinge în stiva de apeluri va fi primul care iese din stivă și rulează. Acest lucru va asigura că JavaScript menține ordinea corectă de execuție a funcției.
JavaScript va scoate fiecare cadru din stivă până când acesta este gol, ceea ce înseamnă că toate funcțiile s-au terminat de rulat.
Libuv Web API
La baza programelor asincrone JavaScript se află libuv. Biblioteca libuv este scrisă în limbajul de programare C, care poate interacționa cu sistemul de operare API-uri de nivel scăzut. Biblioteca va furniza mai multe API-uri care permit codului JavaScript să ruleze în paralel cu altele cod. API-uri pentru crearea de fire, un API pentru comunicarea între fire și un API pentru gestionarea sincronizării firelor.
De exemplu, când utilizați setTimeout în Node.js pentru a întrerupe execuția. Cronometrul este configurat prin libuv, care gestionează bucla de evenimente pentru a executa funcția de apel invers odată ce întârzierea specificată a trecut.
În mod similar, atunci când efectuați operațiuni de rețea în mod asincron, libuv gestionează aceste operațiuni într-un mod neblocant. mod, asigurându-se că alte sarcini pot continua procesarea fără a aștepta ca operația de intrare/ieșire (I/O) să Sfârşit.
Coada de apel invers și de evenimente
Coada de apel invers și de evenimente este locul în care funcțiile de apel invers așteaptă execuția. Când se finalizează o operație asincronă din libuv, funcția de apel invers corespunzătoare este adăugată la această coadă.
Iată cum decurge secvența:
- JavaScript mută sarcinile asincrone în libuv pentru ca acesta să se ocupe și continuă să se ocupe de următoarea sarcină imediat.
- Când sarcina asincronă se termină, JavaScript adaugă funcția sa de apel invers la coada de apel invers.
- JavaScript continuă să execute alte sarcini din stiva de apeluri până când se termină cu totul în ordinea curentă.
- Odată ce stiva de apeluri este goală, JavaScript se uită la coada de apel invers.
- Dacă există un apel invers în coadă, îl împinge pe primul în stiva de apeluri și îl execută.
În acest fel, sarcinile asincrone nu blochează firul principal, iar coada de apel invers asigură executarea apelurilor lor corespunzătoare în ordinea în care au fost finalizate.
Ciclul buclei de evenimente
Bucla de evenimente are, de asemenea, ceva numit coada de microtask. Această coadă specială din bucla de evenimente conține microtask-uri programate să se execute de îndată ce sarcina curentă din stiva de apeluri se finalizează. Această execuție are loc înainte de următoarea redare sau iterație a buclei de eveniment. Microtask-urile sunt sarcini cu prioritate ridicată, cu prioritate față de sarcinile obișnuite în bucla de evenimente.
O microsarcină este de obicei creată atunci când lucrați cu Promises. Ori de câte ori o Promisiune rezolvă sau respinge, ea corespunde .apoi() sau .captură() callbacks se alătură coadei de microtask. Puteți utiliza acea coadă pentru a gestiona sarcinile care necesită execuție imediată după operațiunea curentă, cum ar fi actualizarea interfeței de utilizare a aplicației dvs. sau gestionarea modificărilor de stare.
De exemplu, o aplicație web care realizează preluarea datelor și actualizează interfața de utilizare pe baza datelor preluate. Utilizatorii pot declanșa această preluare de date făcând clic în mod repetat pe un buton. Fiecare clic pe buton inițiază o operație de recuperare a datelor asincronă.
Fără microsarcini, bucla de evenimente pentru această sarcină ar funcționa după cum urmează:
- Utilizatorul face clic pe butonul în mod repetat.
- Fiecare clic pe buton declanșează o operație de preluare a datelor asincronă.
- Pe măsură ce operațiunile de preluare a datelor se termină, JavaScript adaugă apelurile lor corespunzătoare în coada de activități obișnuită.
- Bucla de evenimente începe procesarea sarcinilor din coada obișnuită de activități.
- Actualizarea interfeței de utilizare bazată pe rezultatele preluării datelor se execută de îndată ce sarcinile obișnuite o permit.
Cu toate acestea, cu microtask-uri, bucla de evenimente funcționează diferit:
- Utilizatorul face clic pe butonul în mod repetat și declanșează o operație de preluare a datelor asincronă.
- Pe măsură ce operațiunile de preluare a datelor se termină, bucla de evenimente adaugă apelurile lor corespunzătoare la coada de microsarcini.
- Bucla de evenimente începe procesarea sarcinilor din coada de microtask imediat după finalizarea sarcinii curente (clic pe buton).
- Actualizarea interfeței de utilizare bazată pe rezultatele preluării datelor se execută înainte de următoarea sarcină obișnuită, oferind o experiență de utilizator mai receptivă.
Iată un exemplu de cod:
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);
});
});
În acest exemplu, fiecare clic pe butonul „Preluare” apelează fetchData(). Fiecare operațiune de preluare a datelor este programată ca o microsarcină. Pe baza datelor preluate, actualizarea interfeței de utilizator se execută imediat după finalizarea fiecărei operațiuni de preluare, înainte de orice alte sarcini de randare sau buclă de evenimente.
Acest lucru asigură că utilizatorii văd datele actualizate fără a întâmpina întârzieri din cauza altor sarcini din bucla de eveniment.
Folosirea microtask-urilor în astfel de scenarii poate preveni deformarea UI și poate oferi interacțiuni mai rapide și mai fluide în aplicația dvs.
Implicații ale buclei de evenimente pentru dezvoltarea web
Înțelegerea buclei de evenimente și a modului de utilizare a caracteristicilor sale este esențială pentru construirea de aplicații performante și receptive. Bucla de evenimente oferă capabilități asincrone și paralele, astfel încât să puteți gestiona eficient sarcinile complexe din aplicația dvs., fără a compromite experiența utilizatorului.
Node.js oferă tot ce aveți nevoie, inclusiv lucrători web pentru a obține paralelism suplimentar în afara firului principal al JavaScript.