Programmazione dinamica per il web
Nota iniziale
Se l'HTML rappresenta lo scheletro e il CSS l'estetica visiva, JavaScript è la mente logica e i muscoli della pagina web. È lo strumento che trasforma un documento di testo statico in un'applicazione vivente, capace di reagire al passaggio del mouse, inviare e ricevere informazioni dal server in background e mutare aspetto dinamicamente.
In questa guida non ti sommergerò di sintassi astratta ed esercizi puramente accademici. Lavoreremo invece sulla costruzione di solidi modelli mentali: capiremo come ragiona il motore del browser, come domare l'asincronia delle chiamate web e come gestire l'interfaccia utente in modo pulito e sicuro.
Apri la console degli strumenti sviluppatori e iniziamo a scrivere la logica del nostro codice.

1. La mente del Browser: cos'è JS e come funziona
Nota storica importante per fare bella figura alle interviste di lavoro: JavaScript non ha nulla a che fare con Java. Hanno in comune solo le prime quattro lettere per pure ragioni di marketing risalenti alla metà degli anni '90. JS è nato per girare direttamente nei browser, rendendo le pagine interattive.
I browser moderni eseguono JavaScript a velocità supersonica grazie a motori complessi come **V8** (usato da Chrome, Edge e Node.js). Questi motori compiono una compilazione chiamata **JIT (Just-In-Time)**: convertono il codice sorgente in codice macchina super-ottimizzato una frazione di secondo prima di eseguirlo.
La metafora della cucina con un solo chef (Single Thread ed Event Loop)
Il concetto più importante da tenere a mente è che JavaScript è **single-threaded**: ha una sola linea di esecuzione, il che significa che può fare una sola cosa alla volta.
Immagina la linea di esecuzione come una **cucina con un solo chef**. Se un cliente ordina una bistecca che richiede 20 minuti di cottura (una richiesta di rete o l'attesa di un file dal server), lo chef non può bloccarsi fermo davanti alla griglia senza fare nient'altro, altrimenti la cucina (il tuo sito) rimarrebbe congelata e i clienti se ne andrebbero infuriati.
Ecco come risolve il problema JavaScript:
- Lo chef mette la bistecca sulla griglia ed imposta un timer.
- Nel frattempo, lo chef continua a preparare antipasti e insalate per gli altri ordini (continua l'esecuzione del codice sincrono).
- Il controllo della cottura viene delegato a un assistente esterno alla cucina (le **Web API del browser**).
- Quando il timer suona, l'assistente inserisce la bistecca cotta in un nastro trasportatore che scorre all'ingresso della cucina (la **Callback Queue**).
- Lo chef controlla costantemente se il tagliere principale è libero. Non appena finisce di preparare gli ordini correnti (grazie all'**Event Loop**), prende la bistecca dal nastro e la impiatta per servirla.
Questo permette a JavaScript di gestire operazioni lunghissime senza mai congelare l'interfaccia utente.
2. Il tag Script: la battaglia di async vs defer
Per agganciare un file JavaScript a una pagina HTML usiamo il tag `<script src="script.js"></script>`. Ma dove va posizionato questo tag? Di default, quando il browser incontra uno script mentre analizza l'HTML, interrompe la costruzione della pagina per scaricare ed eseguire il file, rallentando drasticamente il caricamento visivo.
Per risolvere questo problema, gli sviluppatori moderni inseriscono il tag nell'head della pagina usando due attributi magici:
<!-- Scarica in background, esegue IMMEDIATAMENTE appena pronto (può bloccare l'HTML) -->
<script src="statistiche.js" async></script>
<!-- Scarica in background, esegue SOLO quando l'HTML è stato interamente analizzato -->
<script src="app-logica.js" defer></script>Quale scegliere?
- `async`: perfetto per script esterni che non dipendono da altri file e non hanno bisogno di interagire subito con la pagina (es. i pixel di tracciamento di Google Analytics o Facebook). Non viene garantito l'ordine di esecuzione.
- `defer`: lo standard per i tuoi file di logica. Garantisce che gli script vengano eseguiti nell'ordine esatto in cui sono scritti nell'HTML, e solo dopo che la struttura della pagina è del tutto pronta, evitando errori in cui il codice tenta di modificare tag non ancora creati.
3. Variabili, Scope e addio definitivo a var
In JavaScript lo **Scope** definisce la zona del codice in cui una variabile è visibile e utilizzabile. Esistono tre livelli di scope:
- Scope Globale: visibile ovunque nel file.
- Scope di Funzione: visibile solo all'interno della funzione in cui è stata dichiarata.
- Scope di Blocco: visibile solo all'interno di una coppia di parentesi graffe `{ ... }` (come in un ciclo `for` o un costrutto `if`).
Il problema di var e la stabilità di let/const
In passato in JavaScript si usava solo `var`. Tuttavia, `var` **ignora completamente lo scope di blocco**, "scappando" all'esterno di cicli e condizioni e creando bug silenziosi e difficili da trovare. JavaScript moderno introduce `let` e `const` che rispettano rigorosamente lo scope di blocco:
if (true) {
var scappa = "Sono visibile fuori!";
let protetta = "Rimango qui dentro.";
}
console.log(scappa); // Stampa "Sono visibile fuori!"
console.log(protetta); // ReferenceError! (Corretto e sicuro)const blocca il riferimento, non l'oggetto
Molti sviluppatori credono erroneamente che dichiarare un oggetto con `const` lo renda immutabile. Non è così: `const` impedisce di **riassegnare il valore** della variabile ad un altro oggetto, ma ti permette di **modificare le proprietà interne** dell'oggetto stesso:
const utente = { nome: "Chiara" };
utente.nome = "Sofia"; // Funziona perfettamente! L'oggetto viene modificato.
// utente = { nome: "Marco" }; // TypeError! (La riassegnazione è bloccata)Regola aurea: usa sempre `const` come scelta predefinita. Passa a `let` solo se sai che il valore di quella variabile dovrà essere riassegnato (es. nei contatori dei cicli). Bandisci definitivamente `var` dal tuo codice.
4. Tipi di dati, Referenze e l'uguaglianza dei cloni
I tipi di dati in JavaScript si dividono in due grandi categorie logiche:
- Tipi Primitivi (String, Number, Boolean, Null, Undefined, Symbol, BigInt): vengono passati **per valore**. Se copi una variabile, crei un clone del tutto indipendente.
- Tipi di Riferimento (Object, Array, Function): vengono passati **per riferimento** (indirizzo di memoria). Copiare un oggetto copia solo la chiave per aprirlo in memoria: modificando la copia, modificherai l'originale.
let nome1 = "Alice";
let nome2 = nome1; // Copia il valore
nome2 = "Bob";
console.log(nome1); // "Alice" (Rimane intatta)
let array1 = [1, 2];
let array2 = array1; // Copia il riferimento in memoria
array2.push(3);
console.log(array1); // [1, 2, 3] (Modificata anche l'originale!)L'uguaglianza debole (==) vs stretta (===)
Non usare mai l'operatore di uguaglianza debole `==`. Tenta di convertire automaticamente i tipi diversi prima di confrontarli, producendo risultati insensati come `"" == 0` (true) o `0 == false` (true).
Usa **sempre** l'operatore di uguaglianza stretta `===` (e il suo opposto `!==`), che controlla sia il valore sia il tipo senza compiere conversioni:
console.log(5 == "5"); // true (Conversione automatica di tipo)
console.log(5 === "5"); // false (Tipi diversi: Number vs String)5. Funzioni: Classic vs Arrow (e il mistero di this)
Le funzioni racchiudono blocchi di codice riutilizzabili. Oltre alle funzioni classiche, in JavaScript moderno usiamo molto le Arrow Functions (funzioni a freccia).
Le funzioni classiche godono dell'**hoisting**: puoi invocarle nel file prima ancora della riga in cui le hai dichiarate, perché il browser le carica in memoria all'avvio dello script. Le Arrow Functions non hanno questa proprietà e offrono una sintassi ultra-compatta:
const quadrato = x => x * x; // Ritorno implicito ad una rigaIl grande dilemma di 'this'
La differenza fondamentale risiede nel funzionamento della parola chiave `this`:
- Nelle **funzioni classiche**, `this` fa riferimento a *chi chiama la funzione* a runtime (contesto dinamico).
- Nelle **Arrow Functions**, `this` è **lessicale**: eredita il valore dal contesto circostante in cui la funzione è stata scritta, senza curarsi di chi la invoca.
const carrello = {
prodotti: ["Libro", "Penna"],
stampa() {
// Arrow function eredita 'this' dal metodo stampa (quindi fa riferimento a carrello)
this.prodotti.forEach(prodotto => {
console.log(this); // Stampa l'oggetto carrello
});
}
};6. Oggetti, Array ed operatori moderni
Per scrivere codice elegante e pulito, devi conoscere le scorciatoie sintattiche introdotte di recente.
Destrutturazione (Destructuring)
Permette di estrarre proprietà da oggetti o elementi da array e memorizzarli in variabili dedicate in un colpo solo:
const impostazioni = { tema: "dark", volume: 80 };
const { tema, volume } = impostazioni; // Estratti in variabili indipendentiOperatore Spread (...)
Rappresentato da tre punti, "distribuisce" gli elementi di un array o le proprietà di un oggetto all'interno di un nuovo contenitore. È il modo standard per clonare o unire dati in modo sicuro senza modificarli all'origine:
const votiMatematica = [7, 8];
const tuttiVoti = [...votiMatematica, 9, 10]; // [7, 8, 9, 10] (Nuovo array clonato)Evitare i cicli manuali
Non usare cicli `for` manuali per trasformare i dati degli array. JavaScript offre metodi funzionali pronti all'uso come `.map()` per modificare ogni elemento, `.filter()` per scremare gli elementi secondo una condizione, e `.reduce()` per accumulare i valori.
7. DOM Manipulation: l'interfaccia si risveglia
Il DOM (Document Object Model) è la struttura ad albero creata dal browser che rappresenta la tua pagina. JavaScript la interroga e modifica in tempo reale.
Selezionare elementi con querySelector
Usa esclusivamente i metodi moderni basati sui selettori CSS:
- `document.querySelector(".classe")`: restituisce il *primo* elemento corrispondente alla classe.
- `document.querySelectorAll(".classe")`: restituisce una collezione ciclabile di *tutti* gli elementi corrispondenti.
Modificare lo stile in sicurezza
Non scrivere mai proprietà CSS direttamente nel codice JavaScript (es. `elemento.style.color = "red"`). È una cattiva pratica che rende la manutenzione difficilissima.
La prassi corretta è **definire gli stati grafici nel CSS** (es. creare una classe `.is-active` con le sue regole) e usare JavaScript unicamente per **aggiungere o rimuovere quella classe** nel DOM:
const popup = document.querySelector(".popup");
popup.classList.add("is-active");
popup.classList.toggle("is-open"); // Aggiunge se manca, rimuove se c'ètextContent vs innerHTML: Sicurezza XSS
Se devi stampare un testo inserito dall'utente in una pagina, usa sempre `textContent`. Evita `innerHTML`, poiché interpreta i tag HTML e potrebbe permettere a malintenzionati di iniettare script dannosi nel tuo sito (attacco Cross-Site Scripting - XSS).
8. Catturare i click: la gestione degli Eventi
Il browser genera un evento ogni volta che accade qualcosa (un click, il movimento del mouse, la digitazione su tastiera o il caricamento completato).
Legare un comportamento ad un evento si fa agganciando un ascoltatore direttamente da JavaScript:
const pulsante = document.querySelector("#btn-invia");
function gestisciInvio(event) {
event.preventDefault(); // Blocca l'invio del form e il ricaricamento della pagina!
console.log("Dati inviati in modo asincrono.");
}
pulsante.addEventListener("click", gestisciInvio);L'uso di `event.preventDefault()` è cruciale nei form per fermare il comportamento predefinito del browser e permetterci di gestire l'invio in background tramite JavaScript.
9. L'Asincronia spiegata agli umani: async/await
Quando interroghi un server esterno per scaricare dei dati, non puoi sapere quanto tempo ci vorrà per ricevere la risposta. JavaScript gestisce queste attese tramite le **Promise**.
Una Promise può trovarsi in tre stati: `Pending` (in attesa), `Fulfilled` (risolta con successo), `Rejected` (fallita con un errore).
La semplicità di async/await
Per non scrivere codice disordinato basato su costrutti complessi, usiamo la sintassi moderna `async/await`. Questa ti permette di scrivere codice asincrono con la stessa struttura lineare del codice sincrono riga per riga:
// Contrassegniamo la funzione come asincrona
async function caricaDati() {
console.log("Caricamento avviato...");
// Il browser aspetta la risposta della fetch senza bloccare il resto della pagina
const risposta = await fetch("https://api.esempio.com/prodotti");
const prodotti = await risposta.json();
console.log("Prodotti ricevuti:", prodotti);
}
caricaDati();
console.log("Questo log appare PRIMA dei prodotti, perché l'API è asincrona!");10. Evitare l'autodistruzione: try/catch
Se JavaScript incontra un errore irreversibile (ad esempio una chiamata API che fallisce per assenza di rete o un JSON corrotto), interrompe bruscamente l'esecuzione. In questo stato, tutti i tuoi script smetteranno di funzionare.
Per evitare crash catastrofici, avvolgi il codice a rischio in un blocco `try/catch` per gestire l'errore con grazia:
async function scaricaProfilo() {
try {
const risposta = await fetch("https://api.esempio.com/profilo");
const dati = await risposta.json();
console.log("Profilo caricato:", dati.nome);
} catch (errore) {
// Eseguito solo in caso di errore nel blocco try
console.error("Non è stato possibile caricare il profilo:", errore.message);
mostraMessaggioDiErroreAllUtente();
} finally {
// Eseguito sempre, a prescindere dal successo o dal fallimento
nascondiSpinnerCaricamento();
}
}11. Web Storage: salvare le preferenze dell'utente
Se l'utente imposta il tema scuro sul tuo sito, vuoi che questa scelta rimanga salvata anche se chiude il browser e torna il giorno dopo. Per farlo usiamo il localStorage.
localStorage vs sessionStorage
Entrambi salvano dati in coppia chiave-valore nel browser sotto forma di stringhe, ma con una differenza di ciclo di vita:
- `localStorage`: i dati non hanno scadenza e rimangono memorizzati nel browser finché non vengono cancellati via codice o dall'utente.
- `sessionStorage`: i dati vengono persi immediatamente non appena la scheda (tab) del browser viene chiusa.
// Salvare un dato
localStorage.setItem("temaScuro", "true");
// Leggere un dato
const tema = localStorage.getItem("temaScuro");
// Rimuovere un dato
localStorage.removeItem("temaScuro");Salvare oggetti complessi
Poiché il localStorage accetta **solo stringhe**, se provi a salvare un oggetto direttamente, verrà convertito nel testo inutile `"[object Object]"`. Per salvare strutture complesse (oggetti o array), dobbiamo prima convertirli in stringhe JSON con `JSON.stringify`, e riconvertirli in oggetti all'estrazione con `JSON.parse`:
const carrello = ["Scarpe", "Zaino"];
// Salvataggio (conversione in stringa JSON)
localStorage.setItem("carrello_utente", JSON.stringify(carrello));
// Estrazione (conversione in array utilizzabile)
const datiSalvati = localStorage.getItem("dati_utente");
const carrelloArray = datiSalvati ? JSON.parse(datiSalvati) : [];Avviso di sicurezza: non salvare mai password, token di autenticazione o dati personali sensibili nel localStorage. Qualsiasi script JavaScript in esecuzione sulla pagina può accedervi liberamente, rendendoli facili bersagli in caso di attacchi XSS.
12. I comportamenti più folli di JavaScript (e come sopravvivere)
JavaScript è stato scritto di fretta nei primi anni '90 e si trascina dietro alcune stranezze storiche che fanno sorridere gli sviluppatori ma disorientano i principianti. Vediamo le 4 più celebri e come risolverle:
Non è un errore di JavaScript, ma del modo in cui i computer convertono i numeri decimali in frazioni binarie. Per confrontare i prezzi in un e-commerce senza frazioni microscopiche storte, arrotonda sempre i decimali usando `toFixed()` o lavorando con i numeri interi (es. converti in centesimi prima di fare i calcoli):
const totale = (0.1 * 10 + 0.2 * 10) / 10; // Restituisce esattamente 0.3!Questo è un vero e proprio bug storico del motore originale di JavaScript del 1995. Non può essere rimosso o corretto perché farlo romperebbe milioni di siti web attualmente attivi su internet. Per controllare se una variabile è effettivamente un oggetto escludendo `null`, fai un doppio controllo:
if (variabile && typeof variabile === "object") { ... }`NaN` rappresenta il risultato matematico di un'operazione non valida (es. `"ciao" * 5`). In JavaScript, `NaN` è l'unico valore al mondo che non è uguale a se stesso se confrontato con `===`. Per verificare se un valore è `NaN`, usa il metodo nativo:
Number.isNaN(valore); // Restituisce true o false in modo affidabileSe sommi un array vuoto a un altro array vuoto (`[] + []`) ottieni una stringa vuota `""`. Se sommi un array vuoto ad un oggetto vuoto (`[] + {}`) ottieni la stringa `"[object Object]"`. Per evitare questi bug silenziosi, usa sempre l'uguaglianza stretta `===` ed evita di sommare variabili di tipo diverso senza averle prima convertite esplicitamente (es. usando `Number()` o `String()`).
13. Debugging professionale: oltre il console.log
Stampare variabili ovunque con `console.log()` è il metodo più immediato ma disordinato per fare debug. I browser offrono strumenti avanzati per analizzare lo stato del codice.
- `console.table()`: se gli passi un array di oggetti, lo formatta in una magnifica tabella ordinabile nella console.
- `console.dir()`: mostra l'elenco delle proprietà dell'oggetto DOM come oggetto JavaScript puro, anziché stamparne l'HTML.
La forza dell'istruzione debugger
Se inserisci la parola chiave **`debugger;`** nel tuo script e tieni aperti gli strumenti sviluppatori del browser, l'esecuzione del codice si congelerà all'istante in quel punto preciso:
function calcolaPrezzo(base) {
const tasse = 1.22;
debugger; // Il browser congela la pagina ed evidenzia questa riga nei DevTools
return base * tasse;
}Potrai ispezionare il valore corrente di ogni variabile in quel millisecondo e procedere riga per riga, trovando i problemi senza sporcare il codice con log di prova.
14. Sintassi rapida (Cheat Sheet)
Ecco un riepilogo rapido delle principali logiche di interrogazione e modifica del DOM:
| Metodo | Parametro / Uso | Cosa fa |
|---|---|---|
| querySelector() | '.classe' | '#id' | Seleziona il primo elemento corrispondente al selettore CSS indicato. |
| addEventListener() | 'click', callback | Aggancia un ascoltatore per eseguire codice JavaScript in risposta all'evento. |
| classList.add() | 'is-active' | Aggiunge una classe CSS all'elemento per cambiarne lo stato visivo in sicurezza. |
| textContent | 'Testo puro' | Legge o scrive il testo all'interno dell'elemento (sicuro contro attacchi XSS). |
15. Sfida pratica: Mettiti alla prova
Metti in pratica le logiche del DOM e degli eventi in JavaScript:
Crea un contatore dinamico di caratteri in tempo reale per una casella di testo (textarea), che cambi colore (es. diventa rosso) quando si supera il limite di caratteri impostato.
Istruzioni passo-passo:- Seleziona la textarea usando
document.querySelector('.messaggio')ed il contatore usandodocument.querySelector('.counter'). - Aggiungi un ascoltatore di eventi per l'evento
'input'alla tua casella di testo. - All'interno della funzione di callback dell'ascoltatore, leggi la lunghezza corrente del testo digitato.
- Aggiorna la proprietà
textContentdel contatore mostrando i caratteri correnti (es.msgLen + " / 100").
16. Dove continuare dopo
Hai fatto i primi grandi passi nella logica di programmazione per il web. Ecco dove orientare le tue prossime letture per integrare queste competenze:
- NPM e Vite: il tutorial indispensabile per organizzare progetti complessi dividendo il codice in file autonomi (moduli import/export) anziché scrivere script singoli enormi.
- DevTools del Browser: approfondisci il funzionamento del pannello Network e dello strumento di ispezione del codice sorgente.
- Accessibilità web di base: scopri come garantire che il tuo codice JavaScript non ostacoli la navigazione tramite tastiera o screen reader.
- Tutti i tutorial: torna all'indice delle guide per continuare a imparare.
17. Verifica la tua preparazione (Quiz)
Metti alla prova quello che hai imparato in questa guida con questo rapido test di 10 domande a scelta multipla.