JavaScript Real World Vademecum
Parte III: DOM, Eventi e Network
JavaScript prende vita quando interagisce con il Browser. Qui impariamo a manipolare l'HTML (DOM), ascoltare le azioni dell'utente (Eventi) e comunicare con server esterni (Asincronia/Fetch).
DOM e Interattività
21. Caricamento Script
Se il DOM è la "casa" (la struttura HTML) che il browser costruisce, il tuo file JavaScript è l'"impianto elettrico" e gli "elettrodomestici" (l'interattività). La domanda fondamentale è: quando fai entrare l'elettricista?
Posizionamento Tag <script>
Pensa al browser come a un operaio 👷♂️ che legge le istruzioni (il tuo file HTML) dall'alto verso il basso, una riga alla volta, e costruisce la casa.
Il Problema: <script> in <head> (Il Modo "Vecchio" Sbagliato)
Se metti l'elettricista (<script>) nell'<head> (le fondamenta), succede un disastro:
- L'operaio legge
<html>, legge<head>. - Trova
<script src="app.js">. - L'operaio si ferma. Smette immediatamente di costruire la casa. Questo si chiama render-blocking.
- Va a cercare il furgone dell'elettricista (scarica il file
app.js). - Aspetta che l'elettricista faccia tutto il suo lavoro (esegue il file
app.js). - Solo allora, l'operaio torna a leggere le istruzioni e a costruire il
<body>(i muri, le stanze, i mobili).
Perché è un disastro?
Se nel tuo app.js c'è scritto document.getElementById("bottone"), lo script viene eseguito al punto 5, ma il "bottone" (che è nel <body>) non esiste ancora! Verrà costruito solo al punto 6. Il tuo script cercherà un bottone in una casa senza muri. Risultato: null e un crash dell'applicazione.
La Soluzione "Classica": <script> alla Fine del <body>
Per decenni, la soluzione è stata mettere l'elettricista come ultima cosa da fare:
...
<button id="bottone">Cliccami</button>
</body>
<script src="app.js"></script> </html>
- Come funziona: L'operaio costruisce tutta la casa (
<head>,<body>,bottone, tutto). Solo quando ha finito, come ultimissima istruzione, fa entrare l'elettricista. - Vantaggio: L'elettricista entra e vede tutta la casa già costruita.
document.getElementById("bottone")funziona al 100%. - Svantaggio: Se l'impianto elettrico è enorme (un file JS da 5MB), l'utente vedrà una casa costruita ma spenta (non interattiva) per secondi, finché il furgone dell'elettricista non finisce di scaricare.
Attributo defer (Best Practice)
defer è la soluzione moderna e intelligente. È un'istruzione speciale che dai all'operaio.
Pensa a defer come a dire all'operaio: "Vedi quel furgone dell'elettricista? Inizia pure a fargli scaricare tutti i cavi mentre tu continui a costruire i muri (download non-blocking). Potrà entrare a collegare i fili solo quando avrai finito la struttura, ma non preoccuparti di aspettare che abbia finito di lucidare le prese" (esegue dopo il parsing, prima di DOMContentLoaded).
Si usa così, nell'<head>:
<head>
...
<script src="app.js" defer></script>
</head>
- Come funziona:
- Il browser vede
<script defer>nell'<head>. - Inizia a scaricare
app.jsin sottofondo. - NON SI FERMA! Continua a costruire il
<body>(non è render-blocking). - Quando ha finito di leggere tutto l'HTML, esegue lo script
app.js(che era già pronto). - Infine, dichiara la casa pronta (evento
DOMContentLoaded).
- Il browser vede
È il meglio di entrambi i mondi: il download inizia presto, non blocca la costruzione della casa, e l'esecuzione è garantita dopo che tutti gli elementi esistono.
E async?
async è un altro attributo, ma è un "elettricista indisciplinato". Scarica in parallelo, ma poi interrompe l'operaio ed esegue il codice appena ha finito di scaricare, anche se la casa è a metà. È utile per script che non dipendono dal DOM (es. Google Analytics), ma defer è quasi sempre la scelta migliore e più sicura per i tuoi script principali.
window.onload vs DOMContentLoaded
Questi sono eventi, sono i "permessi di lavoro" che dicono al tuo codice: "OK, ora puoi partire".
-
DOMContentLoaded(Il Permesso dell'Architetto) Questo evento scatta non appena l'operaio ha finito di leggere le istruzioni (HTML) e ha costruito la struttura (il DOM).Analogia: È l'architetto che dice: "I muri portanti e il tetto sono su. La casa è strutturalmente pronta." Cosa non aspetta: Non aspetta che i mobili siano consegnati (immagini), che le tende siano appese (CSS), o che gli inquilini arrivino (iframe). Perché è il migliore: È veloce. Il tuo JavaScript può rendere la pagina interattiva molto prima che tutte le pesanti immagini finiscano di caricare.
// Si usa così:
window.addEventListener('DOMContentLoaded', () => {
// Il DOM è pronto!
const bottone = document.getElementById("bottone");
bottone.addEventListener("click", () => alert("Ciao!"));
});(Se usi
defer, il tuo script viene eseguito proprio prima di questo evento, quindi spesso non hai nemmeno bisogno di aspettarlo!) -
window.onload(Il Permesso del Proprietario di Casa) Questo evento è il "vecchio modo". È molto più lento e paziente. Scatta solo quando letteralmente tutto è stato caricato.Analogia: È il proprietario di casa che dice: "Ok, la casa è finita, i mobili sono dentro, le tende sono appese, le luci funzionano e il giardino è piantato. Ora potete entrare." Il Problema: Se hai una galleria di 50 immagini pesanti, il tuo JavaScript (e la tua app) rimarrà "congelato" e non interattivo finché l'ultima immagine non è stata scaricata. È pessimo per l'esperienza utente (UX).
// Vecchio stile (da evitare se non per motivi specifici)
window.onload = () => {
// Tutto (incluse le immagini) è caricato.
};
Regola Pratica: Usa sempre <script defer> nell'<head>. Se per qualche motivo non puoi, metti il tuo codice dentro un listener DOMContentLoaded. Evita window.onload a meno che tu non debba davvero aspettare il caricamento delle immagini.
22. DOM Manipulation - Il Ponte con il Browser
Se il tuo file HTML è il progetto di una casa, il DOM (Document Object Model) è la casa reale costruita dal browser. È una struttura viva e interattiva fatta di oggetti.
Il tuo JavaScript, quindi, non legge il file HTML. Il tuo JavaScript è l'arredatore d'interni, l'elettricista e la squadra di demolizione che entrano nella casa già costruita (il DOM) per spostare mobili, dipingere pareti e installare interruttori.
Manipolare il DOM è l'atto di usare JavaScript per modificare questa casa.
Selezione di Elementi
Prima di poter dipingere un muro, devi trovarlo. La selezione è come dare un indirizzo preciso alla tua squadra di lavoro.
-
getElementById(id)(Il Più Veloce) Questo è come avere il codice fiscale di un elemento. È il modo più veloce e diretto per trovare un elemento unico.// HTML: <div id="header-principale">...</div>
// Nota: non serve il '#' nel nome!
const header = document.getElementById("header-principale"); -
querySelector(selettore)(Il GPS Moderno) Questo è lo strumento più flessibile e moderno. È come un GPS: puoi dargli qualsiasi tipo di indirizzo (un selettore CSS) e lui troverà il primo elemento che corrisponde.// Trova per ID
const header = document.querySelector("#header-principale");
// Trova per Classe (il primo)
const primoBottone = document.querySelector(".btn-primary");
// Trova per Tag e Attributo
const inputEmail = document.querySelector('input[type="email"]'); -
querySelectorAll(selettore)(Il Censimento) Simile al GPS, ma fa il censimento: crea una lista (unaNodeList) di tutti gli elementi che corrispondono al selettore.const tuttiIBottoni = document.querySelectorAll(".btn");
// Ora puoi ciclarli:
tuttiIBottoni.forEach(bottone => {
bottone.style.color = "red";
}); -
getElementsByClassName(classe)(Il Vecchio Metodo "Live") Questo metodo, comegetElementsByTagName, è il "vecchio modo". È ancora utile, ma ha una stranezza importante: restituisce unaHTMLCollection.- La Stranezza (
HTMLCollectionLive): Pensa a unaHTMLCollectioncome a un feed di una videocamera di sicurezza. È live. Se un nuovo elemento con quella classe viene aggiunto alla pagina dopo che hai eseguito il comando, apparirà magicamente nella tua variabile! - Una
NodeList(daquerySelectorAll) è invece una fotografia. È statica. Non si aggiorna se la pagina cambia. - Svantaggio:
HTMLCollectionnon è un vero array. Manca di metodi moderni come.forEach().
- La Stranezza (
-
Convertire
NodeList/HTMLCollectionin Array Per usare la potenza dei metodi array (.map,.filter, ecc.) su unaNodeListoHTMLCollection, devi prima "trasformarla" in un vero array. Analogo: È come prendere i dati grezzi dal feed della videocamera e stamparli su fogli di carta che puoi manipolare.// Metodo Moderno (Spread Operator '...')
const arrayDiElementi = [...document.getElementsByClassName("mia-classe")];
// Metodo Formale
const arrayDiElementi2 = Array.from(document.querySelectorAll(".btn"));
// Ora puoi usare .map()!
arrayDiElementi.map(el => el.textContent);
Proprietà vs Metodi (Distinzione)
Questa è una distinzione filosofica fondamentale. Come distingui un sostantivo da un verbo?
-
Proprietà (I Sostantivi: "Cosa è") Sono i dati di un elemento. Sono valori che leggi o assegni usando il simbolo
=. Non hanno le parentesi(). Analogo:altezza,colore,peso,testo scritto.// Assegnare un valore a una proprietà
elemento.textContent = "Nuovo testo";
input.value = "Valore predefinito";
elemento.id = "nuovo-id"; -
Metodi (I Verbi: "Cosa fa") Sono le azioni che un elemento può compiere. Li chiami usando le parentesi
(). Analogo:clicca!,aggiungiFiglio!,rimuoviti!.// Chiamare un metodo
elemento.addEventListener("click", miaFunzione);
elemento.remove();
container.appendChild(nuovoElemento);
L'Errore Ricorrente: Confondere i due è l'errore più comune.
// SBAGLIATO ❌
elemento.textContent("Testo"); // Errore: textContent non è una funzione!
// CORRETTO ✅
elemento.textContent = "Testo"; // È una proprietà, si assegna!
// SBAGLIATO ❌
elemento.addEventListener = "click"; // Errore: addEventListener è un metodo, va chiamato!
// CORRETTO ✅
elemento.addEventListener("click", () => {}); // Si chiama con ()!
Attributi HTML vs Proprietà DOM
Questo è sottile. Qual è la differenza tra il progetto e la casa reale?
-
Attributo HTML (Il Progetto blueprints): È quello che scrivi letteralmente nel tuo file
.html. È la dichiarazione iniziale.<input type="text" value="Valore Iniziale"> -
Proprietà DOM (La Realtà fisica): È la proprietà viva sull'oggetto JavaScript che il browser crea.
const input = document.querySelector("input");input.value; // "Valore Iniziale"
Per la maggior parte del tempo, le proprietà e gli attributi sono "riflessi": se cambi uno, cambia anche l'altro.
input.id = "test" -> L'HTML ora è <input id="test">
La Differenza Cruciale (con value):
Cosa succede se l'utente digita nell'input?
- L'utente scrive "Ciao".
- La proprietà
input.value(la realtà) diventa"Ciao". - L'attributo HTML (il progetto) rimane
"Valore Iniziale"!
L'attributo rappresenta il valore di default (quello che carichi), la proprietà rappresenta il valore attuale.
Regola Pratica: Lavora sempre con le proprietà (es. input.value, input.id, input.className). È più veloce e diretto. Usa elemento.setAttribute() solo per attributi personalizzati (come data-*) o quando vuoi modificare il "valore di default" del progetto.
Creazione e Aggiunta Elementi
Non sei limitato a modificare ciò che esiste. Puoi costruire stanze nuove!
-
document.createElement(tag)(La Fabbrica) Questo comando crea un nuovo elemento in memoria (nel "magazzino"), non ancora sulla pagina.const nuovoParagrafo = document.createElement("p");
nuovoParagrafo.textContent = "Sono nuovo!";In questo momento,
nuovoParagrafoesiste, ma l'utente non lo vede. È un muro costruito in fabbrica, in attesa di essere installato. -
container.appendChild(elemento)(L'Installazione) Questo è l'atto di prendere l'elemento creato e "installarlo" nella casa. Lo aggiunge come ultimo figlio dell'elementocontainer.// Trova la "stanza" dove vuoi installarlo
const container = document.querySelector("#main-content");
// Installa il muro
container.appendChild(nuovoParagrafo);
// ORA l'utente lo vede!
Modifica del Contenuto
Ci sono diversi modi per cambiare cosa c'è scritto in un elemento, ognuno con i suoi pro e contro.
-
textContent(La Scelta Sicura ✅)- Cosa fa: Imposta o legge solo il testo puro all'interno di un elemento. Ignora qualsiasi HTML.
- Analogo: È come scrivere su un muro con un pennarello. Se provi a scrivere
<strong>Ciao</strong>, vedrai letteralmente il testo "<strong>Ciao</strong>" sul muro. - Perché usarlo: È veloce e sicuro al 100% contro attacchi XSS (Cross-Site Scripting). Se un utente scrive codice malevolo (
<script>...) nel suo nome,textContentlo tratterà come testo innocuo.
elemento.textContent = "Ciao mondo!"; -
innerHTML(La Scelta Potente e Pericolosa ⚠️)- Cosa fa: Imposta o legge l'HTML completo all'interno di un elemento.
- Analogo: È come una penna magica di Harry Potter. Se scrivi
<strong>Ciao</strong>, crea veramente del testo in grassetto. - Il Pericolo: Se scrivi
<script>...(magari proveniente da un input utente), la penna magica lo eseguirà. Questo è un enorme buco di sicurezza. - Regola: Usa
innerHTMLSOLO se 1) sei tu a scrivere l'HTML, o 2) la fonte è 100% sicura. Non usarlo mai per mostrare dati inseriti da un utente.
-
innerText(La Scelta Confusa ❓)- Cosa fa: È il "cugino strano". Prova a leggere il testo così come lo vede l'utente. Ignora elementi nascosti da CSS (
display: none) e interpreta gli spazi e gli "a capo". - Perché evitarlo: È molto più lento di
textContent(deve calcolare il layout CSS) e ha comportamenti imprevedibili. Il 99% delle volte, vuoitextContent.
- Cosa fa: È il "cugino strano". Prova a leggere il testo così come lo vede l'utente. Ignora elementi nascosti da CSS (
-
insertAdjacentHTML(posizione, testoHTML)(Il Chirurgo) È comeinnerHTML, ma più preciso. Invece di rimpiazzare tutto, ti permette di "iniettare" HTML in 4 punti precisi rispetto all'elemento:'beforebegin': Prima dell'elemento.'afterbegin': Dentro l'elemento, prima di tutto il resto.'beforeend': Dentro l'elemento, dopo tutto il resto (comeappendChild).'afterend': Dopo l'elemento.
Modifica degli Stili
Come per il contenuto, hai due modi: quello diretto (e sporco) e quello pulito (e preferito).
-
style.property(camelCase) (Il Modo Diretto/Sporco) Ti permette di impostare stili inline (quelli nell'attributostyle="..."). Analogo: Prendere un secchio di vernice e buttarlo sul muro con le mani. Funziona, ma è un casino da pulire e sovrascrive tutto.const elemento = document.querySelector("#avviso");
// NOTA: 'background-color' (CSS) diventa 'backgroundColor' (JS)
elemento.style.backgroundColor = "red";
elemento.style.fontSize = "20px";
elemento.style.display = "block"; // Per mostrare
elemento.style.display = "none"; // Per nascondereSvantaggio: Crea stili inline, che hanno una "specificità" altissima e sono difficili da sovrascrivere con i tuoi file CSS. Mescola la logica (JS) con la presentazione (CSS).
-
classList(La Best Practice ✅) Questo è il modo professionale. Analogo: Invece di verniciare il muro, attacchi un'etichetta al muro che dice "Muro-Importante". Poi, in un foglio di stile (CSS) separato, definisci.Muro-Importante { background-color: red; }. Questo separa le responsabilità: JavaScript gestisce lo stato (l'etichetta), CSS gestisce l'aspetto.// CSS:
// .is-hidden { display: none; }
// .is-active { color: blue; }
// JS:
elemento.classList.add("is-active"); // Aggiunge la classe
elemento.classList.remove("is-hidden"); // Rimuove la classe
elemento.classList.toggle("evidenziato"); // Aggiunge se non c'è, rimuove se c'è
elemento.classList.contains("is-active"); // trueÈ più pulito, più facile da manutenere e non incasina la specificità CSS.
Modifica Attributi/Proprietà
-
Proprietà
disabled(true/false) Questa è una proprietà booleana fondamentale per i form. È l'interruttore On/Off per un input.const mioBottone = document.querySelector("#submit-btn");
// Per disabilitare (l'utente non può cliccare)
mioBottone.disabled = true; // Appare grigio, non cliccabile
// Per riabilitare
mioBottone.disabled = false;Quando un input è
disabled, il suo valore non viene inviato con il form. -
disabledvsreadonlyQuesta è una distinzione chiave per la UX:disabled: L'elemento è spento. L'utente non può cliccarci, non può farci focus (con Tab), e il suo valore non viene inviato. È un muro.readonly: L'elemento è bloccato in sola lettura. L'utente può vederlo, può farci focus (e copiarne il testo), ma non può modificarlo. Il suo valore viene inviato con il form.
Analogo:
disabledè una porta sbarrata.readonlyè una porta di vetro chiusa a chiave (puoi guardare dentro, ma non toccare).
Navigazione DOM
-
Selettore CSS: Child Combinator (
>) Questo è uno strumento di selezione potente.div p(Selettore Discendente - lo Spazio): Analogo: "Trova tutti ipche sono discendenti di undiv". Questo include figli, nipoti, pronipoti... chiunque viva dentro ildiv.div > p(Selettore Figlio - il>): Analogo: "Trova tutti ipche sono figli diretti (immediati) di undiv". Questo ignora i nipoti. È molto più specifico.
Perché usarlo? Previene che il tuo stile o script "trapeli" in componenti annidati più profondamente. Ti dà un controllo chirurgico sulla selezione.
23. Eventi - Ascoltare e Reagire
Gli eventi sono il sistema nervoso della tua applicazione. Ogni click, movimento, pressione di tasto è un segnale che puoi intercettare per far scattare una logica. Senza eventi, il DOM è solo una scultura statica. Con gli eventi, diventa un robot interattivo.
onclick vs addEventListener - Due Filosofie
Esistono due modi per "collegare un filo" a un sensore (come un bottone).
-
onclick(Il Modo Vecchio / La Linea Telefonica Singola) Questo è un attributo (o proprietà) dell'elemento. È semplice e diretto.const bottone = document.querySelector("#mio-bottone");
bottone.onclick = function() {
console.log("Cliccato!");
};Il Problema: È come una linea telefonica singola. Puoi collegare solo una funzione a
onclick. Se provi a collegarne un'altra, la seconda sovrascrive la prima. -
addEventListener(Il Modo Moderno / Il Centralino) Questo è un metodo. È come un centralino telefonico: puoi "aggiungere" (add) più "ascoltatori" (Listener) allo stesso evento (click).// Aggiungi il primo ascoltatore
bottone.addEventListener("click", function() {
console.log("Primo gestore: salvo i dati.");
});
// Aggiungi un secondo ascoltatore
bottone.addEventListener("click", function() {
console.log("Secondo gestore: invio analytics.");
});Entrambi vengono eseguiti! È più pulito, più flessibile e ti permette di rimuovere (
removeEventListener) un gestore specifico senza toccare gli altri. Regola: UsaaddEventListenersempre. È la best practice professionale.
Riferimento (fn) vs Esecuzione (fn())
Questo è l'errore concettuale più comune e critico da capire.
Analogia: Pensa di dover dare un'istruzione a un assistente.
-
Riferimento (
miaFunzione):bottone.addEventListener("click", miaFunzione);Questo è come dare all'assistente un manuale di istruzioni (la funzionemiaFunzione) e dirgli: "Quando il telefono squilla (l'eventoclick), allora leggi ed esegui le istruzioni in questo manuale." Stai passando la ricetta, non la torta. -
Esecuzione (
miaFunzione()):bottone.addEventListener("click", miaFunzione());// SBAGLIATO ❌ Questo è come cucinare la torta subito, dare la torta (il risultato della funzione, che spesso èundefined) all'assistente e dirgli: "Quando il telefono squilla, dai al cliente questa torta". JavaScript eseguemiaFunzione()immediatamente (una sola volta, al caricamento della pagina) e assegna il suo risultato (undefined) al listener. Il click non farà mai nulla.
Regola: Quando assegni un gestore di eventi, devi passare il nome della funzione (il riferimento), non il risultato della sua esecuzione.
L'Oggetto event
Quando un evento si scatena (l'utente clicca), il browser non si limita a eseguire la tua funzione. La esegue passandole automaticamente un oggetto speciale come primo argomento. Pensa a questo oggetto (e o event) come al report dettagliato dell'incidente.
bottone.addEventListener("click", function(event) { // Eccolo!
console.log(event); // Ispezionalo! È pieno di informazioni
// Informazioni utili:
console.log(event.type); // "click"
console.log(event.timeStamp); // Quando è successo
console.log(event.ctrlKey); // Ha premuto Ctrl mentre cliccava?
console.log(event.clientX, event.clientY); // Dove ha cliccato
});
e.target
Questa è la proprietà più importante dell'oggetto event.
Analogia: Se l'oggetto event è il report dell'allarme antincendio, e.target è il sensore specifico che è scattato.
È un riferimento diretto all'elemento HTML che ha originato l'evento.
// 'e.target' è il bottone esatto che è stato cliccato
container.addEventListener("click", function(e) {
console.log(e.target); // Mostra l'elemento HTML cliccato
});
e.preventDefault()
Questo è il tuo freno di emergenza per fermare il comportamento predefinito del browser. Certi elementi HTML hanno un "lavoro" predefinito:
- Cliccare su un link (
<a>) ti fa cambiare pagina. - Premere "Invio" su un form (
<form>) fa ricaricare la pagina.
e.preventDefault() dice al browser: "Grazie, ma non farlo. Lascia gestire a me (JavaScript)."
const mioForm = document.querySelector("#mio-form");
mioForm.addEventListener("submit", function(e) {
// 1. FERMA IL REFRESH DELLA PAGINA!
e.preventDefault();
// 2. Ora posso prendere i dati con JS senza che la pagina sparisca
const nome = document.querySelector("#nome").value;
console.log(`Ciao, ${nome}!`);
});
Event Delegation - L'Ascoltatore Intelligente
Questo è un pattern fondamentale per la performance.
- Il Problema: Immagina di avere una lista (un
<ul>) con 1000 elementi (<li>). Devi aggiungere un listener a ognuno di essi? Sarebbe un incubo di performance (1000 fili collegati) e non funzionerebbe per i nuovi<li>aggiunti dopo. - La Soluzione: Sfrutta il "Bubbling" (gli eventi "risalgono" come bolle). Metti un solo ascoltatore sul genitore (
<ul>). - Analogia: Invece di mettere una guardia del corpo su ogni persona in una stanza (
<li>), metti un solo buttafuori all'unica porta d'uscita (<ul>).
const lista = document.querySelector("#lista-spesa");
// Un solo listener sul genitore!
lista.addEventListener("click", function(e) {
// 'e.target' è il sensore che ha scatenato (l'<li> cliccato)
// Chiediamo al "buttafuori":
// "L'elemento cliccato (e.target) è un LI?"
if (e.target.tagName === "LI") {
console.log("Hai cliccato su:", e.target.textContent);
e.target.classList.toggle("comprato");
}
});
Vantaggi:
- Performance: Un solo listener invece di 1000.
- Dinamicità: Funziona automaticamente anche per i nuovi
<li>che aggiungi in futuro!
Il Problema di Contesto: this e addEventListener
Questo è un problema avanzato che colpisce quando usi le Classi (OOP).
this è una parola chiave "camaleontica", il suo significato cambia in base a chi sta chiamando la funzione.
- Il Problema:
Quando
addEventListeneresegue la tua funzione (es.this.clearCart), la esegue lui (il listener del bottone). Di conseguenza, all'interno della funzione,thisnon sarà più il tuo carrello, ma diventerà il bottone stesso!
class Carrello {
constructor() {
this.items = ["mela"];
const btn = document.querySelector("#svuota");
// SBAGLIATO ❌
btn.addEventListener("click", this.svuotaCarrello);
}
svuotaCarrello() {
console.log(this); // 'this' qui sarà il <button>, non il Carrello!
// this.items.length = 0; // CRASH! Il bottone non ha 'items'.
}
}
Soluzione: .bind(this)
.bind() è come creare un "clone" della tua funzione con il this permanentemente bloccato.
Analogia: È come dare al tuo assistente (il listener) un manuale di istruzioni (la funzione) con una targhetta con il tuo nome (this) incollata sopra con la supercolla. Non importa chi leggerà quel manuale, saprà sempre che "io" si riferisce a te (l'istanza del Carrello).
// ...dentro il constructor...
// CORRETTO ✅
btn.addEventListener("click", this.svuotaCarrello.bind(this));
this.svuotaCarrello.bind(this) crea una nuova funzione che, quando chiamata, avrà this impostato correttamente sul Carrello.
Soluzione: Arrow Function Wrapper
Questo è il modo più moderno e spesso preferito.
Le Arrow Function (=>) non hanno un loro this! "Ereditano" il this del luogo in cui sono state scritte.
Analogia: È come se tu (il constructor) dicessi al tuo assistente: "Quando il telefono suona, non chiamare tu direttamente il magazzino. Chiama me, e poi io chiamerò il magazzino."
// ...dentro il constructor...
// CORRETTO (e moderno) ✅
btn.addEventListener("click", () => {
// Questa è una arrow function
// Il 'this' qui dentro è lo stesso 'this' del constructor
// (cioè l'istanza del Carrello)
this.svuotaCarrello();
});
Eventi Speciali
-
keydownvskeypress(Deprecato) vskeyupAnalogia: La Macchina da Scriverekeydown(Il Tasto Scende): Il momento in cui il tuo dito preme fisicamente il tasto verso il basso. Rileva TUTTI i tasti (Frecce, Shift, Ctrl, 'a'). Usa questo per i giochi e i controlli.keyup(Il Tasto Risale): Il momento in cui rilasci il tasto.keypress(Il Carattere Appare): Il momento in cui la pressione produce un carattere sulla carta. Ignora Frecce, Shift, Ctrl. È DEPRECATO (obsoleto), non usarlo.
-
changevsinput(UX Feedback) Questa è una distinzione cruciale per la User Experience (UX).change(Quando hai "finito"): Si attiva solo quando l'utente conferma la modifica (di solito cliccando fuori dall'input box o premendo Invio).- Uso: Validazione finale, quando l'utente ha finito di scrivere.
input(In Tempo Reale): Si attiva ad ogni singolo tasto premuto. Se l'utente scrive "Ciao", l'evento si attiva 4 volte.- Uso: Barre di ricerca istantanee, contatori di caratteri, filtri live.
-
submitQuesto evento si attiva solo sull'elemento<form>, quando l'utente prova a inviarlo (premendo Invio o cliccando un<button type="submit">). È qui che devi usaree.preventDefault(). -
DOMContentLoaded(Vedi Sezione 9) L'evento che scatta quando l'HTML è stato costruito, ma prima che le immagini siano caricate. È il "via" standard per il tuo codice.
Interazioni del Browser
-
confirm()(Dialogo Bloccante) Questo non è un evento, ma una funzione che crea un evento di interazione.confirm("Sei sicuro?")mostra una finestra di dialogo nativa (e bruttina) che blocca l'esecuzione di tutto il JavaScript finché l'utente non fa una scelta.console.log("Prima della conferma");
const utenteHaConfermato = confirm("Vuoi davvero eliminare tutto?");
// Il codice si ferma qui e aspetta...
if (utenteHaConfermato) { // 'confirm' restituisce true (OK) o false (Annulla)
console.log("Eliminazione in corso...");
} else {
console.log("Annullato.");
}Attenzione: Blocca l'intera interfaccia. Le app moderne evitano
confirme usano modali personalizzati (come<dialog>) per un'esperienza migliore.
24. Dialog e Modali
Per decenni, per mostrare un "pop-up" (un modale), gli sviluppatori hanno dovuto combattere con div posizionati in modo assoluto, z-index, div semi-trasparenti per lo sfondo (backdrop) e complicate logiche JavaScript per "intrappolare" il focus della tastiera. Era un incubo di accessibilità e manutenzione.
L'elemento <dialog> è la soluzione nativa e moderna del browser a questo problema. È come se il browser ti fornisse un palco portatile pre-costruito.
<dialog> (L'Elemento Palcoscenico)
Pensa all'elemento <dialog> come a un palco che tieni nel backstage (nascosto) del tuo teatro (la pagina web).
<dialog id="mio-dialogo">
<h2>Titolo del Modale</h2>
<p>Questo è un messaggio importante.</p>
<button id="chiudi-btn">Chiudi</button>
<form method="dialog">
<button value="confermato">Conferma</button>
<button value="annullato">Annulla</button>
</form>
</dialog>
<button id="apri-btn">Apri Modale</button>
Di base, questo elemento non fa nulla ed è invisibile. Per farlo "salire sul palco", hai due metodi JavaScript, ed è qui che la magia accade.
showModal() (Modale) vs show() (Non-Modale)
Questa è la distinzione fondamentale. Stai portando l'attore sul palco, ma come?
-
showModal()(Il Faro da Palcoscenico 🔦) Questo è il metodo che vorrai usare il 99% delle volte. È il "vero" modale.Analogo: È come accendere un faro (spotlight) sull'attore (
<dialog>) e abbassare completamente le luci su tutto il resto della pagina (il backdrop).const dialog = document.querySelector("#mio-dialogo");
const apriBtn = document.querySelector("#apri-btn");
apriBtn.addEventListener("click", () => {
dialog.showModal();
});Cosa fa
showModal()automaticamente per te:- Crea un Backdrop: Aggiunge uno sfondo semi-trasparente (
::backdrop) che copre il resto della pagina. - Blocca l'Interazione: L'utente non può cliccare o interagire con nient'altro sulla pagina (link, bottoni, ecc.) finché il modale non è chiuso.
- Intrappola il Focus (Focus Trap): Se l'utente preme
Tab, il focus della tastiera rimane intrappolato all'interno del modale, ciclando solo tra i suoi elementi interattivi. È un requisito fondamentale per l'accessibilità (a11y). - Si chiude con
Esc: L'utente può chiudere il modale semplicemente premendo il tastoEsc.
- Crea un Backdrop: Aggiunge uno sfondo semi-trasparente (
-
show()(Il Pannello Informativo 📰) Questo metodo è per un "non-modale" o "dialogo".Analogo: È come un pannello informativo o un sottotitolo che appare in un angolo dello schermo (come una chat o un avviso di cookie). Puoi ancora interagire con il resto della pagina mentre è visibile.
// Meno comune
dialog.show();Cosa NON fa
show():- Nessun backdrop.
- Nessun blocco dell'interazione.
- Nessun focus trap.
- Il tasto
Escnon lo chiude.
Regola: Usa showModal() per qualsiasi cosa che richieda l'attenzione esclusiva dell'utente (conferme, form, avvisi). Usa show() per notifiche non invadenti (raro).
close() (Uscita di Scena)
Questo è il metodo per chiudere programmaticamente il dialogo.
Analogo: È l'attore che fa l'inchino e esce di scena.
const dialog = document.querySelector("#mio-dialogo");
const chiudiBtn = document.querySelector("#chiudi-btn");
// Modo 1: Chiudere con JavaScript
chiudiBtn.addEventListener("click", () => {
dialog.close(); // Chiude il dialogo
});
// Modo 2: Il trucco del Form (più pulito!)
// Qualsiasi <button> dentro un <form method="dialog">
// chiuderà automaticamente il dialogo quando cliccato.
// NON serve JavaScript!
Il Superpotere: Il returnValue
Quando chiudi un dialogo, puoi opzionalmente passare un "messaggio di chiusura" (un returnValue).
Analogo: È come se l'attore, uscendo di scena, ti consegnasse un bigliettino con sopra scritto cosa ha deciso ("confermato" o "annullato").
<form method="dialog">
<button value="confermato">Conferma</button>
<button value="annullato">Annulla</button>
</form>
// JS per "ascoltare" il bigliettino
const dialog = document.querySelector("#mio-dialogo");
// Ascolta l'evento 'close'
dialog.addEventListener("close", () => {
// Leggi il bigliettino!
console.log("Il dialogo si è chiuso. Valore restituito:", dialog.returnValue);
if (dialog.returnValue === "confermato") {
console.log("L'utente ha confermato!");
//...esegui la logica di conferma...
} else {
console.log("L'utente ha annullato.");
}
});
Se l'utente preme Esc, il returnValue sarà una stringa vuota "" (o il valore di un eventuale bottone cancel).
L'elemento <dialog> elimina da solo il 90% del lavoro sporco e dei bug che affliggevano i modali "fatti a mano", gestendo focus, backdrop e chiusura in modo nativo e accessibile.
Asincronia e Comunicazione Server
25. Il Concetto - Sincrono vs Asincrono
Cosa fa: Definisce come il codice viene eseguito nel tempo.
Il problema: JavaScript è "single-threaded" (ha una sola mano). Se gli fai fare un calcolo lungo in modo sincrono, blocchi tutto il sito finché non finisce.
Analogia: Il Tostapane 🍞
- Sincrono (Blocking): Metti il pane, e resti immobile a fissare il tostapane per 2 minuti finché non scatta. Non puoi fare altro. (Il sito si blocca).
- Asincrono (Non-blocking): Metti il pane, premi la leva, e intanto vai a prendere il latte e il caffè. Quando il tostapane fa TLIN! (callback/promise), torni a prendere il pane. (Il sito rimane fluido).
26. fetch() - L'ordine al ristorante
Cosa fa: Chiede dati a un server esterno (es. meteo, utenti, prodotti).
Il segreto: fetch è un processo a due fasi. Non ricevi subito i dati, ricevi una promessa di riceverli!
fetch('https://api.esempio.com/pizza')
.then(res => res.json()) // 1. Apri la scatola (Parsing)
.then(data => console.log(data)) // 2. Mangia il contenuto (Uso dati)
.catch(err => console.error(err)); // Gestione errori
Analogia: Il doppio ordine al fast food 🍔
- Primo step (La Rete): Ordini e ti danno un cercapersone. Quando vibra, ti danno il vassoio con una scatola chiusa (
res). Non vedi ancora il panino, sai solo che la scatola è arrivata (o se la cucina è esplosa404/500). - Secondo step (Il Parsing): Devi aprire la scatola e scartare il panino. Richiede tempo! Questo è il
res.json(). Quando hai finito, hai finalmente il cibo vero (data).
27. async / await - Il cambio automatico
Cosa fa: È il modo moderno di gestire le attese. Fa sembrare il codice asincrono (complesso) come se fosse codice sincrono (lineare e semplice).
Non sono inseparabili. Spesso si pensa che viaggino per forza insieme, ma fanno due lavori distinti:
async: È l'Autorizzazione. Dichiara a JavaScript: "In questa funzione potrebbero succedere cose che richiedono tempo, tieniti pronto".await: È il Tasto Pausa effettivo. Dice: "Fermati su questa riga esatta e non andare avanti finché non ricevi i dati".
Ricorda:
- Puoi avere
asyncsenzaawait: la funzione girerà velocissima senza mai mettersi in pausa (sintatticamente valido, ma inutile). - NON puoi MAI avere
awaitsenzaasync: se provi a premere il "tasto pausa" in una funzione normale, JavaScript va in panico e genera un errore fatale.awaitesige l'autorizzazioneasync.
// Prima: Cambio Manuale (.then .then) 😫
fetch(url).then(res => res.json()).then(data => ...);
// Dopo: Cambio Automatico (async/await) 😎
async function prendiDati() {
// "await" mette in pausa la funzione finché la promessa non si risolve
const res = await fetch(url); // Aspetta la scatola
const data = await res.json(); // Aspetta di aprirla
console.log(data); // Fatto!
}
Analogia:
.then()è come guidare col cambio manuale: devi gestire ogni marcia, frizione e passaggio di dati. Potente, ma faticoso.awaitè il cambio automatico: tu premi l'acceleratore e il motore gestisce le pause e i cambi di stato per te.
28. try...catch - Il Trapezista
Cosa fa: È la rete di sicurezza universale. Cattura qualsiasi errore, sia che dipenda dall'imprevedibilità del mondo reale (l'utente entra in galleria e perde il 4G, il server ha malfunzionamenti), sia che derivi da un tuo errore di battitura (sincrono).
async function operazione() {
try {
// Il trapezista salta...
const res = await fetch(url);
const data = await res.json();
// Anche se sbagli tu qui, il catch lo prende!
const nome = data.user.toUpperCase();
} catch (err) {
// ...la rete lo prende se il server cade o se tu sbagli!
console.error("Errore tecnico:", err); // Per te
alert("Impossibile caricare i dati"); // Per l'utente
}
}
Analogia:
try { ... }: È il trapezista che tenta il triplo salto mortale.catch { ... }: È la rete sotto. Se il trapezista scivola (fetch fallisce) o se ha un crampo (bug nel codice), cade sicuro nella rete invece di schiantarsi al suolo (crashare l'app).
Regola d'oro: Gestisci due livelli di errori!
- Console (Sviluppatore): La spia del motore 🔧 (
console.errorcon i dettagli tecnici per capire se è colpa tua o del server). - UI (Utente): La spia sul cruscotto 💡 (Messaggio gentile: "C'è stato un problema di connessione, riprova").
Persistenza dei Dati e Storage
29. localStorage - La Memoria a Lungo Termine
Finora, tutte le nostre variabili (let, const) hanno vissuto nella RAM del browser. Sono come appunti scritti su una lavagna bianca: veloci, utili, ma appena l'utente aggiorna o chiude la pagina (F5), la lavagna viene pulita. Tutto svanisce.
Il localStorage è una tecnologia completamente diversa. È la memoria a lungo termine del tuo sito web.
Analogo: Non è una lavagna bianca, è un cassetto della scrivania (o una cassaforte) incorporato nel browser dell'utente. I dati che metti qui dentro (impostazioni, punteggi, un carrello della spesa) sopravvivono ai refresh, alle chiusure del browser e persino allo spegnimento del computer. Quando l'utente torna sul tuo sito dopo una settimana, i dati sono ancora lì ad aspettarlo.
Il Problema Fondamentale: Il Muro delle Stringhe
C'è una regola d'oro, un "però" grande come una casa, che non puoi mai dimenticare: il localStorage può memorizzare solo ed esclusivamente stringhe di testo.
Pensa al localStorage come a un vecchio fax. Un fax non può spedire un pacco (un oggetto), un mazzo di carte (un array), o un interruttore (un booleano). Può spedire solo un foglio di carta piatto (una stringa).
Questo è l'errore che fanno tutti all'inizio:
// Questo è quello che vorresti fare
const utente = {
nome: "Alice",
livello: 12,
isPremium: true
};
// ERRORE COMUNE: Salvare direttamente ❌
localStorage.setItem('utente', utente);
// Cosa hai salvato davvero?
const salvato = localStorage.getItem('utente');
console.log(salvato); // Output: "[object Object]"
Hai perso tutto! Il nome, il livello, tutto è stato schiacciato in quella stringa inutile, "[object Object]". È come aver provato a mandare via fax un pacco: dall'altra parte è arrivato solo un foglio nero e illeggibile.
JSON - Il Traduttore Universale dei Dati
Come risolviamo il problema del fax? Non spediamo il pacco, ma spediamo un documento che descrive il contenuto del pacco, pezzo per pezzo.
JSON (JavaScript Object Notation) è il linguaggio universale per descrivere dati complessi (oggetti, array) in un formato di testo piatto (una stringa).
Analogo: JSON è il manuale di istruzioni IKEA. Prende un mobile complesso (il tuo oggetto) e lo "appiattisce" in un manuale (la stringa). Chiunque riceva il manuale può "ricostruire" un mobile identico.
Ci sono due comandi principali che devi conoscere:
JSON.stringify() (Lo Smontatore/Appiattitore)
JSON.stringify() (letteralmente "string-ifà", trasforma in stringa) è il processo di "smontaggio". Prende il tuo oggetto o array JavaScript vivo e lo converte in una stringa di testo JSON che lo descrive perfettamente.
Analogo: È la macchina che smonta i mobili IKEA e li impacchetta nella scatola piatta.
const impostazioni = {
tema: 'dark',
notifiche: true,
volume: 80,
suoni: {
click: true,
notifica: false
}
};
// La magia dello "smontaggio"
const stringaJSON = JSON.stringify(impostazioni);
console.log(stringaJSON);
// Output (una singola, lunga stringa di testo):
// '{"tema":"dark","notifiche":true,"volume":80,"suoni":{"click":true,"notifica":false}}'
// ORA puoi salvarlo! È solo testo!
localStorage.setItem('settings', stringaJSON);
JSON.parse() (Il Rimontatore/Ricostruttore)
JSON.parse() è l'operazione inversa. Prende una stringa di testo JSON (il "manuale") e la "analizza" (parse), ricostruendo l'oggetto o l'array JavaScript originale.
Analogo: È l'atto di seguire le istruzioni IKEA per rimontare il mobile.
// Recupera la stringa dal localStorage (il "manuale")
const stringaSalvata = localStorage.getItem('settings');
console.log(typeof stringaSalvata); // "string" - è ancora solo testo!
// La magia del "rimontaggio"
const impostazioniRicostruite = JSON.parse(stringaSalvata);
console.log(typeof impostazioniRicostruite); // "object" - è tornato un oggetto!
// Ora puoi usarlo come un normale oggetto JavaScript
if (impostazioniRicostruite.tema === 'dark') {
document.body.classList.add('dark-mode');
}
console.log(impostazioniRicostruite.volume); // 80
Il Ciclo di Vita Completo dei Dati
Questo è il flusso che userai sempre:
// FASE 1: CREAZIONE - Hai i tuoi dati JavaScript (un array)
const todoList = [
{ id: 1, testo: "Imparare localStorage" },
{ id: 2, testo: "Conquistare il mondo" }
];
// FASE 2: SERIALIZZAZIONE (Smontaggio) - Trasformi in stringa
const todoListStringa = JSON.stringify(todoList);
// FASE 3: SALVATAGGIO (setItem) - Metti la stringa nel "cassetto"
localStorage.setItem('todos', todoListStringa);
// ... L'utente chiude il browser, spegne il PC, va a dormire ...
// ... Il giorno dopo riapre il tuo sito ...
// FASE 4: RECUPERO (getItem) - Leggi dal "cassetto"
const todoListRecuperata = localStorage.getItem('todos');
// È ancora una stringa! '[{"id":1,...}, ...]'
// FASE 5: DESERIALIZZAZIONE (Rimontaggio) - Ritrasformi in oggetto
const todoListRicostruita = JSON.parse(todoListRecuperata);
// FASE 6: USO - Lavori con i dati come sempre
console.log(todoListRicostruita[0].testo); // "Imparare localStorage"
Gestire il Primo Avvio - Il Pattern Robusto
Cosa succede quando un utente visita il tuo sito per la prima volta?
Il "cassetto" è vuoto. localStorage.getItem('todos') restituirà null.
Se provi a fare JSON.parse(null), il risultato è null (non un errore). Ma se ci provi a fare .push()... CRASH.
Devi sempre fornire un valore di default (un "Piano B").
-
Il Pattern con
||(OR) - Il Più Comune Questo è il modo più rapido e conciso, che sfrutta i valori "falsy".// Prova a parsare i dati salvati.
// Se `getItem` restituisce `null`, `JSON.parse(null)` restituisce `null`.
// `null` è "falsy", quindi l'operatore OR (||) sceglie il "Piano B": un array vuoto.
const tasks = JSON.parse(localStorage.getItem("data")) || []; -
Il Pattern
try-catch- Il Più Sicuro Cosa succede se i dati nellocalStoragesono corrotti? (es. una stringa JSON scritta male:"{nome: 'mario'}"). In questo caso,JSON.parse()lancerà un errore e farà crashare la tua app. Iltry-catchè la rete di sicurezza che previene questo.function caricaDatiSicuri(key, valoreDefault) {
try {
const item = localStorage.getItem(key);
// Se 'item' è null, il ternario restituisce il default.
// Se 'item' c'è, prova a parsarlo.
return item ? JSON.parse(item) : valoreDefault;
} catch (error) {
// Se il parse fallisce (dati corrotti),
// stampa l'errore e restituisci il default.
console.error(`Errore nel parsing di ${key} dal localStorage:`, error);
return valoreDefault;
}
}
// Uso:
const tasks = caricaDatiSicuri('data', []);
Ispezionare il localStorage nei DevTools
Non devi lavorare alla cieca! Il browser ti dà una finestra segreta per guardare dentro il cassetto del localStorage.
Analogo: È come avere una visione a raggi X sulla scrivania dell'utente.
Come Accedere (in Chrome/Edge/Firefox):
- Apri i DevTools:
F12(o click destro > Ispeziona) - Trova la sezione Storage:
- Chrome/Edge: Tab "Application" → Storage → Local Storage
- Firefox: Tab "Storage" → Local Storage
- Clicca sul tuo dominio (es.
http://127.0.0.1:5500)
Vedrai una tabella semplicissima:
| Key | Value |
|---|---|
settings | {"tema":"dark","notifiche":true,...} |
todos | [{"id":1,"testo":"..."},...] |
Da qui puoi verificare, modificare al volo i valori (doppio click), eliminare singole chiavi (Canc) o svuotare tutto (icona cestino). È lo strumento n.1 per il debug della persistenza.
I Limiti del localStorage - Le Regole del Gioco
Il localStorage è fantastico, ma non è un database infinito. Ha regole e limiti precisi:
- Solo Stringhe: (L'abbiamo già detto, ma è così importante).
- Spazio Limitato (circa 5-10 MB): Analogo: È un cassetto, non un magazzino. È perfetto per impostazioni, un carrello, il nome utente. È pessimo per salvare foto, file audio, o migliaia di record.
- Sincrono (Bloccante):
Quando chiami
localStorage.setItem('roba', '...'), il tuo JavaScript si ferma e aspetta che l'operazione sia scritta sul disco. Se salvi 1KB, è istantaneo. Se provi a salvare una stringa da 4MB, la tua pagina si congelerà (freezerà) per una frazione di secondo. Usalo per dati piccoli e veloci. - Sicurezza Inesistente (È testo in chiaro!):
Analogo: È un post-it appiccicato sullo schermo, non una cassaforte.
Chiunque abbia accesso fisico al browser dell'utente (o tramite un attacco XSS) può aprire i DevTools e leggere tutto quello che c'è dentro.
NON SALVARE MAI:
- Password
- Token API (se non strettamente necessario)
- Numeri di carte di credito
- Qualsiasi dato sensibile.
Best Practices e Pattern Avanzati
- Versionamento: Cosa succede se la
v2della tua app cambia la struttura dei dati (es. dautente: "Mario"autente: { nome: "Mario" })? Devi gestire la migrazione.const APP_VERSION = "v2";
const versioneSalvata = localStorage.getItem("appVersion");
if (versioneSalvata !== APP_VERSION) {
// Logica di migrazione... (es. carica i vecchi dati,
// trasformali, salvali nel new formato)
localStorage.setItem("appVersion", APP_VERSION);
} - Expiration (TTL - Time To Live):
localStoragenon ha una data di scadenza. I dati restano lì per sempre. Se vuoi che scadano (es. un login), devi costruirtelo da solo:function setConScadenza(key, value, ttl_ms) {
const item = {
value: value,
expiry: Date.now() + ttl_ms // Salva il timestamp di scadenza
};
localStorage.setItem(key, JSON.stringify(item));
}
// ...e una funzione get() che controlla il timestamp... - Namespace (Prefissi): Se più script (o app diverse sullo stesso dominio) usano
localStorage, potrebbero sovrascriversi a vicenda (es. entrambi usano la chiaveuser). Analogo: È come etichettare le tue scatole del trasloco con "App. 12B" per non confonderle con quelle dell'"App. 10A".const PREFISSO_APP = "miaApp_";
localStorage.setItem(PREFISSO_APP + 'user', '...');
localStorage.setItem(PREFISSO_APP + 'settings', '...');
I Comandamenti del localStorage 📜
- Salverai Solo Stringhe (Ricorda
JSON.stringify()). - Parserai con Cautela (Ricorda
JSON.parse()). - Non Ti Fiderai Mai (Gestisci il
nulldel primo avvio con|| []). - Proteggerai dai Crash (Usa
try-catchper i dati corrotti). - Non Salverai Dati Sensibili (È un post-it, non una cassaforte).
- Rispetterai i Limiti (Mantieni i dati piccoli, sotto i 5MB).
- Eviterai di Bloccare (Non salvare dati enormi in modo sincrono).
- Ispezionerai nei DevTools (Non lavorare alla cieca).
- Userai un Prefisso (Namespace) (Evita conflitti con altre app).
- Gestirai le Versioni (Prepara il tuo codice a migrare i dati futuri).