Di recente, abbiamo avviato una serie volta a insegnare agli sviluppatori web come funziona JavaScript e quali sono i diversi componenti che rendono il processo di compilazione ed esecuzione del codice in JavaScript semplice e veloce.
Il primo articolo di questa serie era una panoramica del motore JavaScript, dello stack di chiamate e dell’ambiente di runtime. In questa seconda parte di questa serie di tutorial su JavaScript, ci concentreremo sulle parti interne del motore JavaScript e scopriremo perché JavaScript non è più un linguaggio di programmazione interpretato.
Se te lo sei perso o hai bisogno di un aggiornamento, assicurati di leggere la prima parte della nostra serie: Come funziona JavaScript dietro le quinte.
Cos’è il motore JavaScript?
Il motore JavaScript è un programma la cui responsabilità è eseguire codice JavaScript. Tutti i browser moderni sono dotati di una propria versione del motore JavaScript, ma il più popolare è il motore V8 di Google. Il motore V8 di Google alimenta i browser Google Chrome, nonché Node.js. Node.js è un runtime JavaScript utilizzato per creare applicazioni lato server al di fuori del browser.
Ecco un elenco dei diversi motori JavaScript per ciascun browser Internet principale:
- V8 – Motore JavaScript open source sviluppato da Google per Chrome
- Ragno scimmia – Il motore JavaScript che alimenta Mozilla Firefox
- JavaScriptCore – Motore JavaScript open source sviluppato da Apple per Safari
- Rinoceronte – Motore JavaScript open source gestito da Mozilla Foundation per FireFox
- Chakra – Un motore JavaScript per Microsoft Edge
- JerryScript – Un motore JavaScript per il Internet delle cose (Iot).
Ora che abbiamo capito cos’è un motore JavaScript, possiamo dare una sbirciatina più a fondo e conoscere i diversi componenti di JavaScript. Quindi, con questo sguardo al motore dietro di noi, nella prossima sezione, discuteremo di come il codice JavaScript viene compilato in codice macchina in modo che possa essere eseguito.
Leggere: Le 10 migliori alternative di AngularJS
Come funzionano la compilazione e l’interpretazione di JavaScript?
Innanzitutto, capiamo le differenze tra un compilatore e un interprete. Come forse sapranno gli sviluppatori web, un computer può capire solo 0 e 1 (pensa a loro come semplici interruttori di accensione e spegnimento). Ecco perché alla fine ogni programma per computer deve essere convertito in codice macchina. Questa attività viene eseguita utilizzando un processo noto come compilazione o interpretazione. Esaminiamo ciascuno di questi processi nella prossima sezione.
Che cos’è la compilazione in programmazione?
Durante la compilazione, l’intero codice sorgente viene convertito in codice macchina tutto in una volta. Il codice macchina viene scritto in un file portatile che può essere eseguito ovunque, indipendentemente dalla piattaforma o dal sistema operativo. Ci sono due passaggi coinvolti nel processo di compilazione del codice. Nella prima fase viene costruito il codice macchina e nella seconda fase viene eseguito sulla macchina.
L’esecuzione del codice macchina avviene subito dopo la compilazione. Ad esempio, qualsiasi applicazione che stai utilizzando ora sul tuo computer è stata prima compilata e ora puoi eseguirla sulla tua macchina.
Leggere: Panoramica di Garbage Collection in JavaScript
Che cos’è l’interpretazione nella programmazione?
D’altra parte, durante l’interpretazione, l’interprete esegue il codice sorgente e lo esegue riga per riga. A differenza della compilazione, che prevede un processo in due fasi, nell’interpretazione il codice viene letto ed eseguito allo stesso tempo. Naturalmente, il codice sorgente deve ancora essere convertito in linguaggio macchina, ma la conversione del codice non avviene in anticipo, bensì subito prima dell’esecuzione.
Cos’è la compilazione just-in-time?
JavaScript è un linguaggio puramente interpretato. Il problema con l’essere un linguaggio interpretato, tuttavia, è che i linguaggi di programmazione interpretati sono molto più lenti in termini di prestazioni rispetto ai linguaggi compilati. Questo rappresenta un problema per le applicazioni moderne che richiedono un’elaborazione veloce e prestazioni elevate. Immagina di giocare a un gioco online sul tuo browser e, quando sposti un cubo attraverso una tessera, ci vogliono diversi secondi per raggiungere il punto finale. Sarebbe accettabile una tale lentezza?
Molte persone chiamano ancora JavaScript un linguaggio di programmazione interpretato, ma non è più così. L’attuale motore JavaScript utilizza contemporaneamente il concetto di compilazione e interpretazione, noto come Just-in-Time (JIT) compilazione.
In questo approccio, l’intero codice sorgente viene compilato in linguaggio macchina in una volta e quindi viene eseguito. Ci sono ancora due passaggi coinvolti nel in anticipo compilazione, ma non esiste un file portatile da eseguire. Questa è una soluzione perfetta per le odierne applicazioni Web che richiedono prestazioni elevate, poiché questo processo è molto più veloce della semplice esecuzione del codice riga per riga. Questa inclusione della compilazione JIT è il motivo per cui JavaScript, tecnicamente, non è più un linguaggio di programmazione interpretato.
Come funziona la compilazione just-in-time (JIT)?
Quindi, come funziona il compilatore JavaScript Just-in-Time? Quando un nuovo pezzo di codice JavaScript entra nel motore JavaScript, il primo passaggio eseguito è l’analisi del codice. Durante questo processo, il codice viene analizzato in una struttura di dati denominata Albero della sintassi astratta (AST).
L’albero della sintassi astratta prima divide ogni riga di codice in parti significative per JavaScript, come il permettere, staticoo funzione parole chiave. Quindi salva queste parti di codice in una struttura ad albero. Il passaggio successivo controlla se ci sono errori di sintassi e, se non ne vengono trovati, l’albero risultante viene utilizzato per generare il codice macchina.
Ora, diamo un’occhiata a un rapido esempio di JIT in azione. Nel seguente frammento di codice JavaScript, abbiamo dichiarato una variabile sul lato sinistro e, sul lato destro, c’è il suo equivalente AST:
Qui abbiamo dichiarato a cost variabile con il nome val e dargli il valore 45. Come puoi vedere sul lato dell’albero AST, oltre alla dichiarazione, c’è molto codice aggiuntivo. Riesci a immaginare tutto il codice estraneo che verrebbe generato in una grande applicazione?
Nella fase successiva, l’AST generato viene compilato in codice macchina. Quindi questo linguaggio macchina viene eseguito. Questo è il modo in cui JavaScript moderno utilizza la compilazione just-in-time. Ricorda, il processo di esecuzione del codice macchina avviene nello stack di chiamate del motore JavaScript.
Fin qui tutto bene: a questo punto, il nostro codice è in esecuzione e questa dovrebbe essere la fine del processo. Non così veloce: JavaScript ha alcune strategie di ottimizzazione del codice da implementare. All’inizio, JavaScript crea un codice macchina molto non ottimizzato in modo che possa eseguire gli script il più velocemente possibile. Quindi, in background, questo codice non ottimizzato viene ricompilato e ottimizzato, mentre viene eseguito il codice corrente. Questo viene fatto la maggior parte delle volte e dopo ogni ciclo di ottimizzazione, il codice non ottimizzato viene scambiato con il codice più ottimizzato, senza mai interrompere l’esecuzione. Ecco perché il motore JavaScript funziona così velocemente.
Il processo di ottimizzazione del codice avviene in thread speciali, separati dal thread principale. Gli sviluppatori JavaScript non possono accedere all’algoritmo di ottimizzazione del codice dal nostro codice sorgente. Sebbene motori diversi implementino la strategia in modi diversi, in poche parole è così che funziona la moderna compilazione JavaScript Just-in-time.