Frontend

Come funziona JavaScript sotto al cofano

Autore

Manuel Ricci

JavaScript, nato negli anni '90, è oggi uno dei linguaggi di programmazione più influenti nel mondo dello sviluppo web. Progettato originariamente per migliorare le pagine web con script semplici, si è evoluto in un linguaggio robusto e completo, capace di gestire complesse applicazioni web e mobili. La sua interoperabilità con HTML e CSS, insieme alla sua capacità di funzionare sia sul lato client che server (grazie a piattaforme come Node.js), lo rendono un pilastro dello sviluppo moderno.

Caratteristiche chiave di JavaScript

JavaScript si distingue per la sua natura dinamica e flessibile. La caratteristica di essere prototype-based significa che gli oggetti possono ereditare proprietà e metodi da altri oggetti, una differenza sostanziale rispetto alla tradizionale ereditarietà basata su classi. Questo approccio offre un modello più flessibile e meno rigido per la creazione di strutture di oggetti. Le funzioni di prima classe, una caratteristica rara nei linguaggi di programmazione tradizionali, permettono a JavaScript di trattare le funzioni come qualsiasi altro oggetto, rendendolo estremamente potente per tecniche di programmazione funzionale. Il loop di eventi non bloccante, infine, è fondamentale per la gestione efficiente di operazioni asincrone, come le richieste di rete, consentendo a JavaScript di eseguire altre operazioni mentre attende che un evento si verifichi o che una richiesta venga completata.

Il Main thread in JavaScript

Nel contesto di JavaScript, il main thread è il cuore dell'esecuzione del codice. È qui che viene eseguito il codice JavaScript, si gestiscono gli eventi dell'utente, si aggiorna il DOM e si esegue il rendering delle pagine. In un ambiente single-thread come JavaScript, il main thread gioca un ruolo cruciale nel mantenere fluida l'esperienza utente. La gestione efficiente del main thread è quindi essenziale, poiché qualsiasi operazione pesante o bloccante può portare a un'interfaccia utente lenta o non reattiva.

Implicazioni del modello Single-Thread

L'approccio single-thread ha sia vantaggi che svantaggi. Da un lato, semplifica la programmazione evitando complessità legate alla concorrenza e al multithreading, come la gestione delle condizioni di gara o dei blocchi. D'altra parte, può portare a problemi di prestazioni se non gestito correttamente. Ad esempio, lunghi calcoli o operazioni di I/O possono bloccare il thread, impedendo al browser di rispondere agli input dell'utente. Per affrontare questi problemi, JavaScript utilizza un modello asincrono e non bloccante, affidandosi a callback, promesse, e async/await per eseguire operazioni lunghe senza bloccare il main thread.

Performance e il main thread

Le prestazioni sul main thread sono direttamente correlate alla fluidità dell'esperienza utente. Una programmazione inefficiente può portare a rallentamenti o al blocco dell'interfaccia utente. Per questo, è essenziale adottare pratiche di sviluppo che minimizzino l'impatto sul main thread. Questo include l'ottimizzazione del codice per ridurre il tempo di esecuzione, l'uso di Web Workers per delegare operazioni pesanti a thread separati, e l'applicazione di tecniche come il virtual DOM per ridurre il numero di operazioni costose sul DOM reale.

Cos'è il JavaScript Engine

Il motore JavaScript è il cuore di ogni ambiente di esecuzione di JavaScript, come i browser o Node.js. Questo motore è responsabile dell'interpretazione e dell'esecuzione del codice JavaScript. Ecco alcuni punti importanti:

  • Parsing: Il codice JavaScript viene prima analizzato (parsed) e trasformato in una struttura dati comprensibile dal motore, chiamata Abstract Syntax Tree (AST).
  • Compilazione: Molti motori moderni utilizzano tecniche di Just-In-Time (JIT) compilation per convertire l'AST in bytecode o direttamente in codice macchina, migliorando le prestazioni.
  • Ottimizzazione: Durante l'esecuzione, il motore ottimizza il codice sfruttando previsioni su come verrà utilizzato, e de-ottimizza se queste previsioni si rivelano sbagliate.
  • Gestione della memoria: Il motore gestisce la memoria attraverso l'allocazione e la liberazione delle risorse, spesso utilizzando un garbage collector per eliminare gli oggetti non più necessari.

Esempi di motori JavaScript sono V8 (usato in Chrome e Node.js), SpiderMonkey (Firefox) e JavaScriptCore (Safari).

La runtime JavaScript

La runtime JavaScript comprende tutto ciò che è necessario per eseguire il codice JavaScript oltre al motore stesso. Include:

  • API del browser/ambiente: Nel contesto del browser, la runtime include API come il DOM (Document Object Model), AJAX (per le richieste HTTP), e timer (setTimeOut, setInterval). In Node.js, include API per operazioni di I/O, gestione di file e rete.
  • Event Loop: Fondamentale per il modello asincrono di JavaScript. L'event loop gestisce gli eventi e le callback, permettendo a JavaScript di eseguire operazioni non bloccanti.
  • Callback Queue: Le callback da eseguire sono messe in coda nell'event loop. Quando il call stack è vuoto, l'event loop trasferisce le callback dal callback queue al call stack per la loro esecuzione.
  • Call Stack: Dove vengono eseguite le funzioni. Quando una funzione viene chiamata, viene aggiunta al call stack e quando termina la sua esecuzione, viene rimossa.

JavaScript non è interpretato

L'interpretazione JIT (Just-In-Time) in JavaScript è una tecnica avanzata utilizzata per migliorare le prestazioni dell'esecuzione del codice. Per comprendere meglio, esamineremo prima la differenza tra compilazione e interpretazione, e poi discuteremo come JIT si inserisce in questo contesto.

Compilazione vs Interpretazione

  1. Compilazione:

    • Definizione: La compilazione è il processo di conversione del codice sorgente scritto in un linguaggio di programmazione (come C++ o Java) in codice macchina o bytecode, che può essere eseguito direttamente da un computer o una macchina virtuale.
    • Caratteristiche:
      • Pre-elaborazione: Il codice viene completamente tradotto prima dell'esecuzione.
      • Velocità: L'esecuzione del codice compilato è generalmente più veloce perché la conversione è già avvenuta.
      • Portabilità: Il codice compilato deve essere generato specificamente per ogni tipo di hardware.
  2. Interpretazione:

    • Definizione: L'interpretazione è un metodo di esecuzione del codice sorgente in cui un interprete legge, analizza e esegue il codice in tempo reale, senza convertirlo precedentemente in codice macchina.
    • Caratteristiche:
      • Esecuzione Diretta: Il codice viene eseguito direttamente dall'interprete.
      • Flessibilità: Più facile da usare durante lo sviluppo, poiché le modifiche possono essere testate immediatamente.
      • Velocità: Generalmente più lento rispetto alla compilazione a causa dell'overhead dell'interpretazione in tempo reale.

JavaScript JIT

JavaScript è un linguaggio tradizionalmente interpretato. Tuttavia, con l'avvento di motori JavaScript avanzati come V8 (Google Chrome), SpiderMonkey (Firefox) e JavaScriptCore (Safari), è stata introdotta la compilazione JIT.

  1. Cos'è la JIT in JavaScript:

    • Definizione: JIT è un metodo di compilazione in cui il codice sorgente viene compilato in codice macchina in tempo reale, durante l'esecuzione del programma, piuttosto che prima.
    • Funzionamento: Invece di compilare l'intero programma in una volta, JIT compila solo le parti del codice che vengono eseguite frequentemente, note come "hot code".
  2. Vantaggi della JIT:

    • Prestazioni Ottimizzate: Migliora le prestazioni combinando la velocità della compilazione con la flessibilità dell'interpretazione.
    • Ottimizzazione Specifica: JIT può ottimizzare il codice in base al suo utilizzo effettivo durante l'esecuzione.
    • Miglior Utilizzo delle Risorse: Riduce l'uso della memoria e il tempo di avvio rispetto alla compilazione completa.
  3. Sfide della JIT:

    • Complessità: La JIT aggiunge complessità al motore JavaScript.
    • Sicurezza: Potenziali problemi di sicurezza dovuti alla generazione di codice macchina in tempo reale.

Bonus: La Temporal Dead Zone

Non sapevo dove altro metterla, quindi aggiungo questo termine non correlato con tutto il resto. La Temporal Dead Zone (TDZ) è un concetto introdotto in ECMAScript 6 (ES6) per gestire le dichiarazioni di variabili tramite let e const. La TDZ inizia dall'inizio del blocco in cui la variabile è dichiarata e termina quando la variabile viene inizializzata. Durante la TDZ, l'accesso alla variabile risulta in un errore di riferimento, poiché la variabile esiste ma non è ancora stata inizializzata. Questo meccanismo aiuta a catturare errori comuni dovuti all'utilizzo di variabili non inizializzate, migliorando la robustezza e la leggibilità del codice.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.