JavaScript sotto il cofano
Sommario
- Stack di thread e chiamate
- Contesto di esecuzione
- Ciclo di eventi e JavaScript asincrono
- Archiviazione della memoria e raccolta dei rifiuti
- Compilazione JIT (Just In Time).
- Riepilogo
In questo articolo, ci addentreremo nel funzionamento interno di JavaScript e nel modo in cui viene effettivamente eseguito. Comprendendo i dettagli, capirai il comportamento del tuo codice e quindi potrai scrivere app migliori.
JavaScript è descritto come:
Linguaggio di programmazione compilato a thread singolo, garbage collection, interpretato o Just In Time con un ciclo di eventi non bloccante.
Decomprimiamo ciascuno di questi termini chiave.
Stack di thread e chiamate:
Il motore JavaScript è un interprete a thread singolo composto da un heap e da un singolo stack di chiamate utilizzato per eseguire il programma.
Lo stack di chiamate è una struttura di dati che utilizza il principio LIFO (Last In, First Out) per archiviare e gestire temporaneamente l'invocazione della funzione (chiamata).
Significa che l'ultima funzione che viene inserita nello stack è la prima ad essere estratta quando la funzione ritorna.
Poiché lo stack di chiamate è singolo, l'esecuzione delle funzioni viene eseguita una alla volta, dall'alto verso il basso. Significa che lo stack di chiamate è sincrono.
Ora, poiché è sincrono, ti chiederai come può JavaScript gestire le chiamate asincrone?
Bene, il ciclo di eventi è il segreto dietro la programmazione asincrona di JavaScript.
Ma prima di spiegare il concetto di chiamate asincrone all'interno di JavaScript e come sia possibile con il linguaggio a thread singolo, capiamo prima come viene eseguito il codice.
Contesto di esecuzione (EC):
Il contesto di esecuzione è definito come l'ambiente in cui viene eseguito il codice JavaScript.
La creazione di un Execution Context avviene in due fasi:
1. Fase di creazione della memoria:
- Creazione dell'oggetto globale (chiamato oggetto finestra nel browser e oggetto globale in NodeJS).
- Creare l'oggetto "questo" e associarlo all'oggetto globale.
- Configurazione dell'heap di memoria (un heap è un'area di memoria ampia, per lo più non strutturata) per l'archiviazione di riferimenti a variabili e funzioni.
- Memorizzazione di funzioni e variabili nel contesto di esecuzione globale implementando Hoisting .
Ora che conosciamo i passaggi dietro l'esecuzione del codice, torniamo al
Ciclo di eventi:
Innanzitutto, iniziamo osservando questo diagramma:
Abbiamo il motore che consiste di due componenti principali:
* Memory Heap — qui avviene l'allocazione della memoria.
* Call Stack: è qui che si trovano i frame dello stack durante l'esecuzione del codice.
Abbiamo le API Web che sono thread a cui non puoi accedere, puoi semplicemente effettuare chiamate a loro. Sono le parti del browser in cui entra in gioco la concorrenza, come DOM, AJAX, setTimeout e molto altro.
Infine, c'è la coda di richiamata che è un elenco di eventi da elaborare. Ogni evento ha una funzione associata che viene chiamata per gestirlo.
Quindi qual è il compito del ciclo di eventi qui?
L'Event Loop ha un compito semplice: monitorare lo stack di chiamate e la coda di richiamata. Se lo stack di chiamate è vuoto, Event Loop prenderà il primo evento dalla coda e lo invierà allo stack di chiamate, che lo esegue effettivamente.
Tale iterazione è chiamata segno di spunta nell'Event Loop. Ogni evento è solo una funzione di callback.
Archiviazione della memoria e raccolta dei rifiuti:
Per comprendere la necessità della raccolta dei rifiuti, dobbiamo prima capire il ciclo di vita della memoria che è praticamente lo stesso per qualsiasi linguaggio di programmazione, ha 3 passaggi principali.
1. Allocare la memoria.
2. Utilizzare la memoria allocata per leggere o scrivere o entrambi.
3. Rilasciare la memoria allocata quando non è più necessaria.
La maggior parte dei problemi di gestione della memoria si verifica quando proviamo a rilasciare la memoria allocata. La preoccupazione principale che si pone è la determinazione delle risorse di memoria inutilizzate.
Nel caso dei linguaggi di basso livello in cui lo sviluppatore deve decidere manualmente quando la memoria non è più necessaria, i linguaggi di alto livello come JavaScript utilizzano una forma automatizzata di gestione della memoria nota come Garbage Collection (GC).
JavaScript utilizza due famose strategie per eseguire GC: la tecnica del conteggio dei riferimenti e l'algoritmo Mark-and-sweep.
Ecco una spiegazione dettagliata di MDN su entrambi gli algoritmi e su come funzionano.
Compilazione JIT (Just In Time):
Torniamo alla definizione di JavaScript: dice "Linguaggio di programmazione interpretato, compilato in JIT", quindi cosa significa? Che ne dici di iniziare con la differenza tra un compilatore e un interprete in generale?
Come analogia, pensa a due persone con lingue diverse che vogliono comunicare. Compilare è come fermarsi e prendersi tutto il tempo per imparare la lingua, e interpretare sarà come avere qualcuno lì per interpretare ogni frase.
Quindi i linguaggi compilati hanno un tempo di scrittura lento e un tempo di esecuzione veloce e i linguaggi interpretati hanno l'opposto.
Parlando in termini tecnici: la compilazione è un processo di conversione del codice sorgente del programma in codice binario leggibile dalla macchina, prima dell'esecuzione, e un compilatore prende l'intero programma in una volta sola.
D'altra parte, un interprete è un programma che esegue le istruzioni del programma senza richiederne la precompilazione in un formato leggibile dalla macchina e richiede una singola riga di codice alla volta.
Ed ecco che arriva il ruolo di compilazione JIT che sta migliorando le prestazioni dei programmi interpretati. L'intero codice viene convertito in codice macchina in una sola volta e quindi eseguito immediatamente .
All'interno del compilatore JIT, abbiamo un nuovo componente chiamato monitor (noto anche come profiler). Quel monitor osserva il codice mentre viene eseguito e
- Identificare i componenti caldi o caldi del codice, ad esempio: codice ripetitivo.
- Trasforma questi componenti in codice macchina durante il runtime.
- Ottimizza il codice macchina generato.
- Scambia a caldo la precedente implementazione del codice.
Ora che abbiamo compreso i concetti fondamentali, prendiamoci un minuto per mettere tutto insieme e riassumere i passaggi che JS Engine segue durante l'esecuzione del codice:
- Il motore JS prende il codice JS scritto in una sintassi leggibile dall'uomo e lo trasforma in codice macchina.
- Il motore utilizza un parser per esaminare il codice riga per riga e verificare se la sintassi è corretta. In caso di errori, il codice interromperà l'esecuzione e verrà generato un errore.
- Se tutti i controlli vengono superati, il parser crea una struttura di dati ad albero chiamata Abstract Syntax Tree (AST).
- L'AST è una struttura dati che rappresenta il codice in una struttura ad albero. È più facile trasformare il codice in codice macchina da un AST.
- L'interprete procede quindi a prendere l'AST e trasformarlo in IR, che è un'astrazione del codice macchina e un intermediario tra il codice JS e il codice macchina. IR consente anche di eseguire ottimizzazioni ed è più mobile.
- Il compilatore JIT quindi prende l'IR generato e lo trasforma in codice macchina, compilando il codice, ottenendo feedback al volo e utilizzando quel feedback per migliorare il processo di compilazione.
Grazie per aver letto :)
Puoi seguirmi su Twitter e LinkedIn .

![Che cos'è un elenco collegato, comunque? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































