Il modello di esecuzione di JavaScript è sfumato e facile da fraintendere. Imparare a conoscere il ciclo di eventi al suo interno può aiutare.
JavaScript è un linguaggio a thread singolo, creato per gestire le attività una alla volta. Tuttavia, il ciclo di eventi consente a JavaScript di gestire eventi e callback in modo asincrono emulando sistemi di programmazione simultanei. Ciò garantisce le prestazioni delle tue applicazioni JavaScript.
Che cos'è il ciclo di eventi JavaScript?
Il ciclo di eventi di JavaScript è un meccanismo che viene eseguito in background da ogni applicazione JavaScript. Consente a JavaScript di gestire le attività in sequenza senza bloccare il suo thread di esecuzione principale. Questo è indicato come programmazione asincrona.
Il ciclo di eventi mantiene una coda di attività da eseguire e alimenta tali attività a destra API web per l'esecuzione uno alla volta. JavaScript tiene traccia di queste attività e le gestisce ciascuna in base al livello di complessità dell'attività.
Comprendere la necessità del ciclo di eventi JavaScript e della programmazione asincrona. Devi capire quale problema essenzialmente risolve.
Prendi questo codice, per esempio:
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();
Questo codice definisce innanzitutto una funzione chiamata funzione longRunning(). Questa funzione eseguirà una sorta di attività complessa che richiede tempo. In questo caso esegue a per loop iterando oltre 100.000 volte. Ciò significa che console.log("Ciao") corre 100.000 volte.
A seconda della velocità del computer, questo può richiedere molto tempo e bloccarsi shortRunningFunction() dall'esecuzione immediata fino al completamento della funzione precedente.
Per il contesto, ecco un confronto del tempo impiegato per eseguire entrambe le funzioni:
E poi il singolo shortRunningFunction():
La differenza tra un'operazione di 2.351 millisecondi e un'operazione di 0 millisecondi è evidente quando miri a creare un'app performante.
In che modo il ciclo di eventi contribuisce alle prestazioni dell'app
Il loop degli eventi ha diverse fasi e parti che contribuiscono a far funzionare il sistema.
Lo stack di chiamate
Lo stack di chiamate JavaScript è essenziale per il modo in cui JavaScript gestisce le chiamate di funzioni ed eventi dalla tua applicazione. Il codice JavaScript viene compilato dall'alto verso il basso. Tuttavia, Node.js, leggendo il codice, Node.js assegnerà le chiamate di funzione dal basso verso l'alto. Come si legge, spinge le funzioni definite come frame nello stack di chiamate una per una.
Lo stack di chiamate è responsabile del mantenimento del contesto di esecuzione e dell'ordine corretto delle funzioni. Lo fa operando come uno stack LIFO (Last-In-First-Out).
Ciò significa che l'ultimo frame di funzione che il tuo programma inserisce nello stack di chiamate sarà il primo a uscire dallo stack ed essere eseguito. Ciò garantirà che JavaScript mantenga il giusto ordine di esecuzione della funzione.
JavaScript estrarrà ogni frame dallo stack finché non sarà vuoto, il che significa che tutte le funzioni hanno terminato l'esecuzione.
API web Libuv
Al centro dei programmi asincroni di JavaScript c'è libuv. La libreria libuv è scritta nel linguaggio di programmazione C, che può interagire con il sistema operativo API di basso livello. La libreria fornirà diverse API che consentono al codice JavaScript di funzionare in parallelo con altri codice. API per la creazione di thread, un'API per la comunicazione tra thread e un'API per la gestione della sincronizzazione dei thread.
Ad esempio, quando usi setTimeout in Node.js per sospendere l'esecuzione. Il timer viene impostato tramite libuv, che gestisce il ciclo di eventi per eseguire la funzione di callback una volta trascorso il ritardo specificato.
Allo stesso modo, quando esegui operazioni di rete in modo asincrono, libuv gestisce tali operazioni in modo non bloccante modo, assicurando che altre attività possano continuare l'elaborazione senza attendere l'operazione di input/output (I/O). FINE.
La coda di richiamata e di eventi
La coda di callback e di eventi è dove le funzioni di callback attendono l'esecuzione. Quando un'operazione asincrona viene completata da libuv, la funzione di callback corrispondente viene aggiunta a questa coda.
Ecco come va la sequenza:
- JavaScript sposta le attività asincrone su libuv affinché possano essere gestite e continua a gestire immediatamente l'attività successiva.
- Al termine dell'attività asincrona, JavaScript aggiunge la sua funzione di richiamata alla coda di richiamata.
- JavaScript continua a eseguire altre attività nello stack di chiamate fino a quando non viene eseguito tutto nell'ordine corrente.
- Una volta che lo stack di chiamate è vuoto, JavaScript esamina la coda di richiamata.
- Se c'è una richiamata in coda, inserisce la prima nello stack di chiamate e la esegue.
In questo modo, le attività asincrone non bloccano il thread principale e la coda di richiamata garantisce che le corrispondenti richiamate vengano eseguite nell'ordine in cui sono state completate.
Il ciclo del ciclo degli eventi
Il ciclo di eventi ha anche qualcosa chiamato coda di microtask. Questa coda speciale nel ciclo di eventi contiene le microattività pianificate per l'esecuzione non appena l'attività corrente nello stack di chiamate viene completata. Questa esecuzione avviene prima del rendering successivo o dell'iterazione del ciclo di eventi. I microtask sono attività ad alta priorità con precedenza sulle attività regolari nel ciclo di eventi.
Un microtask viene comunemente creato quando si lavora con Promises. Ogni volta che una Promessa si risolve o rifiuta, corrisponde .Poi() O .presa() i callback si uniscono alla coda dei microtask. È possibile utilizzare tale coda per gestire le attività che richiedono un'esecuzione immediata dopo l'operazione corrente, come l'aggiornamento dell'interfaccia utente dell'applicazione o la gestione dei cambiamenti di stato.
Ad esempio, un'applicazione Web che esegue il recupero dei dati e aggiorna l'interfaccia utente in base ai dati recuperati. Gli utenti possono attivare questo recupero di dati facendo clic ripetutamente su un pulsante. Ogni clic sul pulsante avvia un'operazione di recupero dati asincrona.
Senza microtask, il ciclo di eventi per questa attività funzionerebbe come segue:
- L'utente fa clic ripetutamente sul pulsante.
- Ogni clic sul pulsante attiva un'operazione di recupero dati asincrona.
- Al termine delle operazioni di recupero dei dati, JavaScript aggiunge i callback corrispondenti alla normale coda delle attività.
- Il ciclo di eventi avvia l'elaborazione delle attività nella normale coda delle attività.
- L'aggiornamento dell'interfaccia utente basato sui risultati del recupero dei dati viene eseguito non appena le normali attività lo consentono.
Tuttavia, con i microtask, il ciclo di eventi funziona in modo diverso:
- L'utente fa clic ripetutamente sul pulsante e avvia un'operazione di recupero dati asincrona.
- Al termine delle operazioni di recupero dei dati, il ciclo di eventi aggiunge i callback corrispondenti alla coda del microtask.
- Il ciclo di eventi avvia l'elaborazione delle attività nella coda delle microattività immediatamente dopo aver completato l'attività corrente (clic del pulsante).
- L'aggiornamento dell'interfaccia utente basato sui risultati del recupero dei dati viene eseguito prima della successiva attività regolare, fornendo un'esperienza utente più reattiva.
Ecco un esempio di codice:
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);
});
});
In questo esempio, ogni clic sul pulsante "Recupera" chiama recuperaDati(). Ogni operazione di recupero dei dati viene pianificata come un microtask. In base ai dati recuperati, l'aggiornamento dell'interfaccia utente viene eseguito immediatamente dopo il completamento di ogni operazione di recupero, prima di qualsiasi altra attività di rendering o ciclo di eventi.
Ciò garantisce che gli utenti visualizzino i dati aggiornati senza subire ritardi dovuti ad altre attività nel ciclo di eventi.
L'utilizzo di microtask in scenari come questo può impedire il jank dell'interfaccia utente e fornire interazioni più rapide e fluide nell'applicazione.
Implicazioni dell'Event Loop per lo sviluppo web
Comprendere il ciclo di eventi e come utilizzare le sue funzionalità è essenziale per creare applicazioni performanti e reattive. Il ciclo di eventi fornisce funzionalità asincrone e parallele, in modo da poter gestire in modo efficiente attività complesse nella tua applicazione senza compromettere l'esperienza dell'utente.
Node.js fornisce tutto ciò di cui hai bisogno, compresi i web worker per ottenere un ulteriore parallelismo al di fuori del thread principale di JavaScript.