JavaScript sotto il cofano: ciclo di eventi

Dec 01 2022
Padroneggiamo JavaScript esplorando il suo funzionamento da zero Hai mai riscontrato errori non definiti o faticato a identificare l'ambito di una variabile? È davvero dispendioso in termini di tempo eseguire il debug di qualsiasi errore senza sapere come funzionava il codice. In questo blog, dimostrerò come funzionano effettivamente concetti avanzati come il ciclo di eventi in relazione al contesto di esecuzione, allo stack di chiamate e alla coda di richiamata.

Padroneggiamo JavaScript esplorando il suo funzionamento da zero

Foto di Marc St su Unsplash

Hai mai affrontato errori non definiti o faticato a identificare l'ambito di una variabile?

È davvero dispendioso in termini di tempo eseguire il debug di qualsiasi errore senza sapere come funzionava il codice.

In questo blog, dimostrerò come funzionano effettivamente concetti avanzati come event loop, execution context, call stacke .callback queue

Un disclaimer: i concetti sono incredibilmente intensivi di conoscenza e interconnessi, quindi per favore non battere ciglio!

Motore JavaScript

Il motore V8 di Google è un noto esempio di un motore JavaScript. Ad esempio, Chrome e Node.js utilizzano entrambi il motore V8. Fondamentalmente, il motore V8 è composto da due parti:

  1. Stack di chiamate: tutto il codice viene eseguito al suo interno. Funziona come la struttura dati Stack, ovvero seguendo il concetto LIFO (Last In First Out).
  2. Memory Heap: dove si verifica l'allocazione della memoria. A differenza della struttura dati dell'heap, è solo un ricordo.

Il contesto di esecuzione globale o GEC è il contesto di esecuzione predefinito creato dal motore JavaScript ogni volta che viene ricevuto un file di script.

Tutto il codice JavaScript che non si trova all'interno di una funzione viene eseguito in GEC. I seguenti passaggi vengono eseguiti in GEC:

  • Costruisci uno spazio di memoria per memorizzare tutte le variabili e le funzioni su scala globale
  • Genera un oggetto globale.
  • Genera la parola chiavethis

A seconda di dove verrà eseguito il codice determinerà dove thissi trova. Ad esempio, in Node.js punta a un oggetto globale distinto, mentre nel browser punta windowall'oggetto.

Consolle browser

Stack di chiamate (o stack di esecuzione delle funzioni)

JavaScript è single-threaded, come hai già sentito. Ma cosa significa in realtà?

Significa che un motore JavaScript contiene solo uno call stacko function execution stack.

  • Come sappiamo, ogni volta che il compilatore esplora per la prima volta il tuo codice, al motore JS viene chiesto di creare un Global Execution Contexto GECdal compilatore e di inserirlo nel file Call Stack.
  • L'intero codice viene eseguito uno per uno in Global Execution Contexte alloca la memoria per la definizione della funzione o la dichiarazione della variabile e la memorizza lì.
  • Ma quando viene trovata una chiamata di funzione, viene creato un Functional Execution Contexto FECper eseguire il codice della funzione e quindi viene aggiunto all'inizio del file call stack.
  • L'interprete rimuove una funzione da call stackogni volta che la funzione termina. Una funzione termina — quando raggiunge la fine del suo ambito o un'istruzione return.
  • Infine, l'esecuzione dell'intero codice GECviene rimossa dal file Call Stack.

Non preoccuparti, dimostriamolo con un esempio.

function f1{
  console.log('f1');
}

f1();
console.log('end');

Passaggio 2: — Nel nostro esempio, verrà eseguita la prima riga che è f1. Verrà assegnata f1e archiviata una memoria per la sua definizione.

Fase 3: — Nella seconda riga viene richiamata una funzione. Per questa chiamata di funzione, verrà creato un Function Execution Context o FECe verrà memorizzato sopra il file Call Stack.

Passaggio 4: - Ora, l'intero f1()verrà eseguito riga per riga e dopo aver terminato l'esecuzione verrà rimosso dal file Call Stack.

Passaggio 5: — Quindi, l'ultima riga console.log('end')verrà eseguita e stamperà 'end' sulla console. Infine, eseguendo l'intero codice, Global Execution Contextverrà rimosso dal file Call Stack.

In che modo JS gestisce le attività asincrone?

JavaScript, come tutti sappiamo, è un linguaggio sincrono a thread singolo (un'attività alla volta) e il singolo thread che call stackesegue immediatamente qualunque cosa arrivi al suo interno.

Ma cosa succede se dobbiamo eseguire qualcosa dopo 5 secondi? Possiamo indossarlo all'interno del call stack?

No, non possiamo. Perché call stacknon ha nessun timer. Ma come possiamo farlo?

È qui che entra in gioco il runtime JavaScript .

Ambiente runtime JavaScript

Può essere spezzato il cuore se te lo dico ora setTimeout(): non fa parte di JavaScript, anche se console.log()o gli eventi DOM sono tutte parti delle API Web che danno accesso al motore JavaScript per utilizzare tutte le sue proprietà nel Global Execution Context (GEC) tramite l'oggetto globale window. Queste API Web sono denominate asincrone .

Cos'è la coda di richiamata?

Una coda di attività chiamata "coda di richiamata" o "coda di attività" viene eseguita dopo che le attività correnti dello stack di chiamate sono state completate. Le attività registrate nell'API Web vengono spostate dall'API Web alla coda di richiamata.

La coda di richiamata funziona come una struttura di dati della coda, il che significa che le attività vengono gestite in ordine FIFO (first in, first out), al contrario di uno stack di chiamate, il che significa che le attività vengono gestite nell'ordine in cui sono state aggiunte alla coda.

Coda di richiamata

Cos'è il ciclo di eventi?

Un ciclo di eventi JavaScript aggiunge un'attività dalla coda di richiamata allo stack di chiamate in ordine FIFO non appena lo stack di chiamate è vuoto.

Il ciclo di eventi è bloccato se lo stack di chiamate sta attualmente eseguendo del codice e non aggiungerà ulteriori chiamate dalla coda fino a quando lo stack non sarà nuovamente vuoto. Questo perché il codice JavaScript viene eseguito in modalità run-to-complete.

Comprendiamo i concetti di cui sopra con un esempio.

  • All'inizio, Global Execution Contextviene creato per il nostro codice all'interno di our call stacked GECesegue il nostro codice riga per riga.
  • GECesegue la prima riga e stampa 'Start' sulla nostra console.
  • Eseguendo la seconda riga, setTimeout()l'API Web verrà chiamata e quindi setTimeout()consentirà l'accesso alla funzione timer. Quindi sarai in grado di impostare 5000mscome tempo di ritardo.
  • Quando si passa la callBack()funzione tramite setTimeout(), callBack()verrà registrata come API Web di richiamata su web.
  • E quindi GECesegue la prima riga e stampa "End" sulla console.
  • Quando tutto il codice viene eseguito, GECverrà rimosso dal nostro file call stack.
  • Successivamente 5000 millisecond, la callBack()funzione registrata in web API, viene spostata all'interno di call Back Queue.
  • Event loopmette la callBack()funzione in call Stackquando ha finito è tutto lavoro. Infine, la callBack()funzione viene eseguita e stampa "Richiama" nella console.

function f1() {
    console.log('f1');
}

function f2() {
    console.log('f2');
}

function main() {
    console.log('main');
    
    setTimeout(f1, 0);
    
    f2();
}

main();

Se stai pensando che "f1" verrà stampato prima di "f2", allora ti sbagli. Sarà -

main
f2
f1

Il meccanismo discusso di JavaScript è adatto a qualsiasi funzione di callback o richiesta API.

Osserviamo passo dopo passo come funziona il secondo esempio all'interno dell'ambiente di runtime.

  1. Inizialmente GECverrà creato all'interno di call stacke quindi il codice verrà eseguito riga per riga in GEC. Memorizza tutte le definizioni delle funzioni nell'heap della memoria .
  2. Quando main()viene chiamato, Function Execution Context (FEC)viene creato a e quindi entra nello stack di chiamate. Successivamente tutto il codice della main()funzione verrà eseguito riga per riga.
  3. Ha un log della console per stampare la parola main. Quindi console.log('main')viene eseguito ed esce dallo stack.
  4. L' setTimeout()API del browser ha luogo. La funzione di richiamata lo inserisce nella coda di richiamata. Ma nello stack, l'esecuzione avviene come al solito, quindi f2()entra nello stack. Il log della console delle f2()esecuzioni. Entrambi escono dalla pila.
  5. E poi main()salta anche fuori dalla pila.
  6. Il ciclo di eventi riconosce che lo stack di chiamate è vuoto e nella coda è presente una funzione di callback. Quindi, la funzione di callback f1()verrà inserita nello stack dal event loop. L'esecuzione ha inizio. Il registro della console viene eseguito ed f1()esce anche dallo stack. E infine, nient'altro è nello stack e nella coda per essere eseguito ulteriormente.

È la mia pratica e nota. Se lo hai trovato utile, mostra il tuo supporto facendo clic sull'icona di applauso in basso. Puoi seguirmi su medium .