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.
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
| Ambito | Come | Note |
|---|---|---|
| Browser | Nativamente | L'unico linguaggio possibile |
| Server | Node.js, Deno, Bun | API, applicazioni complete |
| Mobile | React Native | Una base di codice, due piattaforme |
| Desktop | Electron | VS 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.