È uscito il Corso Java Completo
Torna al blog

Cos'è JavaScript e come funziona

Cos'è JavaScript, perché i browser eseguono solo lui, come funzionano l'asincronia e async/await, le coercizioni di tipo e il rapporto con TypeScript.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

7 min di lettura

Scrivi tre righe di JavaScript, metti un console.log alla fine, e il risultato esce prima di quello che avevi scritto sopra. Oppure sommi due cose e ottieni una stringa dove aspettavi un numero. In questo articolo trovi perché JavaScript è dove è, come funziona il suo modello asincrono — che è il concetto che lo distingue — e quali sono i suoi limiti reali.

Cos'è

JavaScript è un linguaggio interpretato a tipizzazione dinamica, ed è l'unico che i browser eseguono nativamente: qualunque cosa succeda dentro una pagina web passa da lui.

Nato nel 1995 in dieci giorni per aggiungere piccole interazioni alle pagine, oggi regge applicazioni intere, server, app mobile e programmi desktop.

Quella posizione di monopolio nel browser spiega quasi tutto il resto. Non è diventato il linguaggio più diffuso del mondo perché è il migliore: è diventato tale perché per decenni non c'era alternativa. Se volevi che qualcosa reagisse a un clic, scrivevi JavaScript. Da lì è cresciuto un ecosistema enorme, e quell'ecosistema lo ha poi portato fuori dal browser.

Dove si colloca rispetto agli altri lo trovi in tutti i linguaggi di programmazione.

Il modello a eventi: il codice non aspetta

Questo è il concetto di JavaScript. Capito questo, il resto è sintassi.

JavaScript gira su un solo thread: fa una cosa per volta. Se un'operazione lenta — una richiesta di rete, la lettura di un file — bloccasse quel thread, la pagina si congelerebbe: niente clic, niente animazioni, niente. Per evitarlo, JavaScript non aspetta mai le operazioni lente. Le avvia, va avanti con il resto, e quando il risultato arriva esegue il codice che avevi lasciato in attesa.

console.log("primo");
setTimeout(() => console.log("secondo"), 0);
console.log("terzo");

// Stampa: primo, terzo, secondo

Anche con zero millisecondi di attesa, "secondo" esce per ultimo: la funzione viene messa in coda e viene eseguita solo quando il codice corrente è finito. Il meccanismo che gestisce questa coda si chiama event loop ed è descritto in cos'è Node.js.

Da callback a Promise a async/await

Storicamente il modo di dire "fallo quando il risultato arriva" era passare una funzione: la callback. Funziona, ma incatenando più operazioni una dopo l'altra si ottiene questo:

leggiUtente(id, (utente) => {
  leggiOrdini(utente, (ordini) => {
    calcolaTotale(ordini, (totale) => {
      console.log(totale);
    });
  });
});

Rientra a destra a ogni passaggio, e gestire gli errori significa ripetere un controllo a ogni livello. Lo chiamavano callback hell, e non era un'esagerazione.

Le Promise hanno appiattito quella piramide. Una Promise è un oggetto che rappresenta un risultato non ancora disponibile: ci attacchi cosa fare quando arriva, e la catena resta lineare.

leggiUtente(id)
  .then((utente) => leggiOrdini(utente))
  .then((ordini) => calcolaTotale(ordini))
  .then((totale) => console.log(totale))
  .catch((errore) => console.error(errore));

Con async/await si scrive come se fosse codice normale, pur restando asincrono sotto:

async function mostraTotale(id) {
  try {
    const utente = await leggiUtente(id);
    const ordini = await leggiOrdini(utente);
    console.log(await calcolaTotale(ordini));
  } catch (errore) {
    console.error(errore);
  }
}

await dice: "sospendi questa funzione finché la Promise non si risolve, ma lascia libero il resto del programma". Non blocca il thread, e questa è la differenza che conta. L'errore classico è dimenticare await e ritrovarsi in mano un oggetto Promise invece del valore: quando vedi Promise { <pending> } in console, hai trovato la riga.

L'altro errore frequente è mettere gli await in fila quando le operazioni sono indipendenti. Tre chiamate da un secondo l'una diventano tre secondi invece di uno. Se non dipendono l'una dall'altra, vanno lanciate insieme:

const [utenti, prodotti] = await Promise.all([leggiUtenti(), leggiProdotti()]);

La tipizzazione dinamica e le coercizioni

Le variabili in JavaScript non hanno un tipo dichiarato, e il linguaggio converte automaticamente i valori quando un'operazione lo richiede. Questo produce i comportamenti che girano come barzellette, ma non sono follie: sono regole precise e documentate. Conoscerle è tutta la differenza tra subirle e usarle.

"5" + 3    // "53"   — con le stringhe, + concatena
"5" - 3    // 2      — - non concatena nulla, quindi converte in numero
[] + {}    // "[object Object]"

La regola dietro la prima riga: l'operatore + fa doppio lavoro (somma e concatenazione) e in presenza di una stringa sceglie la concatenazione. Gli altri operatori aritmetici non hanno questa ambiguità e convertono sempre in numero.

Il caso che incontri davvero nel quotidiano è il confronto:

0 == "0"     // true  — converte prima di confrontare
0 === "0"    // false — confronta anche il tipo
null == undefined   // true
null === undefined  // false

Usa sempre ===. Non è un vezzo da puristi: == applica una tabella di conversioni che nessuno ricorda a memoria, e quasi ogni bug che ne deriva è silenzioso.

L'altro punto da sapere sono i valori "falsy", cioè quelli che in un if valgono come falso: false, 0, "", null, undefined, NaN. È il motivo per cui if (contatore) salta il caso in cui il contatore vale zero, che spesso è proprio il caso che ti interessa. Per questo esiste ??, che interviene solo su null e undefined:

const quantita = input ?? 1;   // 0 resta 0
const sbagliato = input || 1;  // 0 diventa 1

Dove gira oggi

AmbitoComeNote
BrowserNativamenteL'unico linguaggio possibile
ServerNode.js, Deno, BunAPI, applicazioni complete
MobileReact NativeUna base di codice, due piattaforme
DesktopElectronVS Code, Slack, Discord

Nel browser JavaScript lavora manipolando il DOM, la rappresentazione della pagina. Scrivere quelle manipolazioni a mano su applicazioni grandi diventa ingestibile, ed è la ragione per cui esistono React e i suoi parenti, o framework completi come Next.js. La divisione dei ruoli tra le due metà è in frontend e backend.

ECMAScript e le versioni

ECMAScript è lo standard; JavaScript è l'implementazione. Dal 2015 esce una versione all'anno, indicata con l'anno (ES2015, ES2020, ES2024). ES2015, che trovi ancora chiamata ES6, è lo spartiacque: ha introdotto let e const, le funzioni freccia, le classi, i moduli e le Promise.

In pratica: se una guida usa var e function() ovunque, è vecchia di dieci anni. Cerca const, let e =>.

Onestà: la flessibilità ha un prezzo

JavaScript ti lascia fare praticamente tutto. Aggiungere proprietà a un oggetto mentre gira, cambiare il tipo di una variabile, passare tre argomenti a una funzione che ne aspetta uno senza che nessuno protesti.

Su uno script di cento righe è una libertà che rende velocissimo scrivere. Su un progetto di centomila righe con cinque persone che ci lavorano, è la stessa libertà che ti lascia scoprire in produzione che una funzione riceveva una stringa dove tutti davano per scontato un numero.

È esattamente il vuoto che riempie TypeScript: non un linguaggio diverso, ma JavaScript con i tipi dichiarati e controllati prima dell'esecuzione. I tipi spariscono alla compilazione, non c'è nulla in più che gira. Su progetti oltre una certa dimensione è ormai la norma, e la ragione è quella: gli errori escono mentre scrivi invece che dopo il rilascio. Il complemento sono i test automatici.

L'altro limite, di natura diversa: l'ecosistema npm è immenso e la tentazione di installare un pacchetto per ogni cosa è forte. Ogni dipendenza è codice di qualcun altro che finisce nel tuo progetto — vale la pena contarle.

In sintesi

JavaScript è l'unico linguaggio che i browser eseguono, e questo spiega la sua posizione più di qualunque merito tecnico.

Il concetto che lo distingue è l'asincronia: gira su un thread solo e non aspetta mai le operazioni lente. Callback, poi Promise, oggi async/await — la sintassi è cambiata, il modello sotto è sempre lo stesso.

Le coercizioni non sono bug ma regole, e la difesa pratica è sempre === e ?? al posto di || quando lo zero è un valore valido.

La flessibilità che lo rende rapido da scrivere è la stessa che lo rende fragile in grande, ed è il motivo per cui i progetti seri aggiungono TypeScript.

Per impararlo, il percorso è in imparare JavaScript da zero. Se stai ancora scegliendo il primo linguaggio, vedi Python o JavaScript e la mappa completa in tutti i linguaggi di programmazione.