JavaScript Real World Vademecum
Parte I: Fondamenti e Sintassi
Benvenuto nelle fondamenta di JavaScript. Prima di costruire applicazioni complesse, dobbiamo padroneggiare i mattoni essenziali: come memorizzare i dati, come prendere decisioni logiche e come automatizzare le ripetizioni.
Fondamenti e Tipi di Dati
1. Variabili - I Contenitori di Dati
Le variabili sono "scatole" etichettate in cui il programma conserva le informazioni. Immagina un trasloco: hai scatole diverse per oggetti diversi. Alcune le apri e le chiudi continuamente, altre le sigilli perché il loro riferimento non deve cambiare.
Ma c'è di più: ogni tipo di scatola ha le sue regole speciali. Alcune scatole possono essere spostate da una stanza all'altra (scope), altre rimangono fisse dove le hai messe. Alcune possono essere svuotate e riempite con oggetti completamente diversi, altre accettano modifiche solo al loro contenuto interno.
let – La Scatola Riutilizzabile (o la Lavagna)
let crea una variabile il cui valore può essere modificato nel tempo. È come una lavagna in cucina dove scrivi la lista della spesa: la aggiorni, cancelli e riscrivi continuamente.
let messaggio = "Ciao";
messaggio = "Arrivederci"; // Perfetto, posso cambiare il valore
let contatore = 0;
contatore++; // Stessa cosa di contatore = contatore + 1
Ma perché si chiama let? Pensa a quando dici "lascia che questa variabile sia..." - è un permesso che dai a JavaScript di avere un contenitore flessibile. È come dire al programma: "Ti lascio gestire questo valore, e ti permetto di cambiarlo quando serve."
Caratteristica Chiave: Block Scope
Una variabile let esiste solo all'interno del blocco {...} in cui è nata. Pensa a una chiave elettronica che funziona solo per una specifica stanza d'albergo: fuori da quella stanza, è inutile.
Questo concetto è rivoluzionario rispetto al vecchio var. È come se ogni coppia di parentesi graffe creasse una bolla invisibile: quello che succede nella bolla, resta nella bolla. Se provi a usare quella variabile fuori dalla sua bolla, JavaScript ti dirà "Non so di cosa stai parlando!"
{
let segreto = "Sono qui dentro";
console.log(segreto); // Funziona!
}
// console.log(segreto); // ERRORE! 'segreto' non esiste qui fuori
Quando usarla?
Quando sai già che il valore di quella variabile dovrà cambiare. Ma non è solo questione di "cambiare" - è questione di intenzione. Usi let quando stai dicendo: "Questa cosa evolverà durante l'esecuzione del mio programma."
Esempi perfetti:
- Contatori: Devono incrementarsi ad ogni ciclo
- Stato temporaneo: Come la posizione attuale in un gioco
- Accumulatori: Quando stai costruendo qualcosa pezzo per pezzo
- Flag di controllo: Variabili che tengono traccia di condizioni che cambiano
const – La Scatola Sigillata (o la Cassaforte)
const crea una variabile che non può essere riassegnata a un nuovo valore o riferimento. È come incidere qualcosa nel marmo: una volta scritto, il riferimento rimane quello.
const PI_GRECO = 3.14159;
// PI_GRECO = 3.14; // ERRORE! Non puoi riassegnare una costante.
Ma attenzione! C'è un trucco mentale importante qui. const non significa "costante" nel senso matematico. Significa "riferimento costante". È la differenza tra dire "questa cassaforte non si può spostare" e "il contenuto della cassaforte non si può toccare".
Concetto Cruciale: Contenitore vs. Contenuto
const blocca il contenitore, non necessariamente il contenuto. Se la variabile const contiene un tipo complesso come un Oggetto o un Array, puoi ancora modificarne le proprietà interne.
const utente = { nome: "Mario" };
utente.nome = "Luigi"; // OK! Stai modificando il contenuto.
// utente = { nome: "Carlo" }; // ERRORE! Stai cercando di cambiare il contenitore.
const numeri = [1, 2, 3];
numeri.push(4); // OK! Stai modificando il contenuto.
// numeri = [5, 6]; // ERRORE! Stai cercando di cambiare il contenitore.
L'analogia della cassaforte bullonata al pavimento è perfetta: non puoi spostare la cassaforte (cambiare il riferimento), ma puoi aprire lo sportello e cambiare gli oggetti che ci sono dentro (modificare le proprietà). È come se const dicesse: "Questa variabile punterà sempre a QUESTO oggetto specifico in memoria, ma quello che c'è dentro l'oggetto può cambiare."
Quando usarla?
Sempre, come prima scelta. Questo è un cambio di mentalità importante: parti sempre da const e passa a let solo quando sei assolutamente certo che dovrai riassegnare la variabile.
Perché? Perché rende il tuo codice più prevedibile. Quando vedi const, sai che quella variabile punterà sempre alla stessa cosa. È una promessa che fai a chi leggerà il codice (incluso il te stesso del futuro): "Questa cosa non cambierà riferimento, puoi fidarti."
var – Il Vecchio Modo (Da Evitare)
var è il modo in cui si dichiaravano le variabili prima di let e const (prima di ES6). Ha un comportamento meno prevedibile (il function scope invece del block scope) che può portare a bug difficili da scovare.
Immagina var come una vecchia serratura che a volte si apre da sola, o come un contenitore che magicamente appare in posti dove non te l'aspetti. Ha questo strano comportamento chiamato hoisting (sollevamento).
Hoisting
JavaScript, prima di eseguire il codice, prende tutte le dichiarazioni var e le "solleva" (hoists) all'inizio della loro funzione (o all'inizio globale), inizializzandole a undefined. È come se il tuo codice venisse riorganizzato a tua insaputa!
// Quello che scrivi
function test() {
console.log(x); // Stampa 'undefined' (non dà errore!)
var x = 5;
console.log(x); // Stampa 5
}
// Quello che JavaScript "vede" e esegue
function test() {
var x; // 1. Dichiarazione "sollevata" e inizializzata a undefined
console.log(x); // 2. Stampa 'undefined'
x = 5; // 3. Assegnazione
console.log(x); // 4. Stampa 5
}
Evitalo nei progetti moderni. Se vedi var in codice vecchio, considera di refactorarlo (sostituirlo con let o const). È come vedere ancora Windows XP in un ufficio nel 2025 - funziona, ma perché rischiare?
null e undefined – L'Assenza Intenzionale vs. Accidentale
Questi due valori rappresentano il "nulla", ma con significati profondamente diversi. È una distinzione sottile ma importantissima che mostra l'intenzione del programmatore.
null
È l'assenza intenzionale di un valore. Sei tu, programmatore, che decidi di assegnare null per indicare che "qui, volutamente, non c'è nulla".
- Analogia più profonda: Un posto a tavola vuoto, ma apparecchiato. Non è che ti sei dimenticato di mettere il piatto - hai consciamente deciso che quel posto deve rimanere vuoto per ora. Magari stai aspettando un ospite che potrebbe arrivare, o forse vuoi segnalare che qualcuno se n'è andato. Il punto è: c'è stata una decisione consapevole.
let canzoneCorrente = null; // "Non c'è nessuna canzone in riproduzione, e lo so"
let utenteSelezionato = null; // "L'utente non ha ancora selezionato nulla"
undefined
È l'assenza accidentale o lo stato di "non ancora definito". È il valore di default di una variabile che è stata dichiarata ma a cui non è ancora stato assegnato un valore. JavaScript lo mette lì automaticamente, come a dire "Boh, non so cosa metterci."
- Analogia più profonda: È come aprire una scatola appena comprata e trovarla vuota - non perché doveva essere vuota, ma perché nessuno ci ha ancora messo niente. O come un modulo con un campo lasciato in bianco - non sai se è stato lasciato vuoto di proposito o se qualcuno si è dimenticato di compilarlo.
let prossimaCanzone;
console.log(prossimaCanzone); // undefined - "Non ho idea di cosa sia"
const utente = { nome: "Mario" };
console.log(utente.eta); // undefined - "Questa proprietà non è stata definita"
La differenza filosofica è profonda: null è il vuoto buddhista - un vuoto pieno di significato. undefined è il vuoto esistenziale - un vuoto che non sa nemmeno di essere vuoto.
2. Tipi di Dati - Le Forme dell'Informazione
In JavaScript, ogni dato ha una sua "forma". Come in cucina usi contenitori diversi per liquidi, solidi e spezie, in programmazione usi strutture diverse per testi, numeri e collezioni di dati. Ma ogni forma ha le sue regole, i suoi superpoteri e le sue limitazioni. Capire queste forme è fondamentale per non fare confusione, come provare a versare della farina in un colino.
Stringhe (String) - Il Testo ✍️
Le stringhe sono sequenze di caratteri. Ma pensarle solo come "testo" è riduttivo. Sono come i mattoncini LEGO del mondo della programmazione: puoi combinarle, spezzarle, trasformarle, cercare al loro interno. Sono la forma che prende qualsiasi informazione che vuoi mostrare o comunicare a un utente.
Template Literals (``)
I backtick (o accenti gravi, ``) sono la scelta migliore e più moderna per creare stringhe. Il loro superpotere è l'interpolazione: ti permettono di inserire variabili o espressioni JavaScript direttamente nel testo usando la sintassi ${...}.
const nome = "Mario";
const eta = 25;
// Vecchio modo (goffo)
const presentazioneVecchia = "Mi chiamo " + nome + " e ho " + eta + " anni.";
// Modo moderno (pulito e leggibile)
const presentazione = `Mi chiamo ${nome} e ho ${eta} anni.`;
// Puoi anche eseguire calcoli dentro ${}
const prezzo = 100;
const messaggio = `Il totale è €${prezzo * 1.22} (IVA inclusa)`;
Ma perché sono così potenti? Perché trasformano la stringa da un blocco monolitico a qualcosa di dinamico e vivo. È come la differenza tra una fotografia (una stringa statica) e un video (un template literal): possono cambiare, adattarsi, reagire ai dati. Inoltre, gestiscono nativamente gli "a capo" senza bisogno di \n.
Caratteri di Escape - I Caratteri Speciali
A volte devi inserire caratteri speciali nel testo. Il backslash \ è il tuo passepartout: dice a JavaScript "il prossimo carattere è speciale, non interpretarlo come un comando".
const negozio = "Sono nel \"Store\""; // Virgolette dentro virgolette
const righe = "Prima riga\nSeconda riga"; // \n = A capo (new line)
const colonne = "Nome\tCognome\tEtà"; // \t = Tab per allineare
const percorso = "C:\\Users\\Documents"; // \\ = Backslash letterale
const apostrofo = 'L\'apostrofo'; // \' = Apostrofo in stringa con apici
È come quando fai le "virgolette con le dita" mentre parli: il backslash è il gesto che dice "attenzione, questo è letterale, non un comando!"
Metodi Utili (La Cassetta degli Attrezzi per Testi)
Ogni stringa in JavaScript è segretamente un oggetto con decine di metodi nascosti. È come se ogni parola che scrivi venisse fornita con un kit completo di strumenti per modificarla.
const testo = "JavaScript è potente";
// Proprietà e metodi di base
testo.length; // 20 - Non è un metodo ma una proprietà!
testo.toUpperCase(); // "JAVASCRIPT È POTENTE"
testo.toLowerCase(); // "javascript è potente"
// Ricerca
testo.includes("potente"); // true - Cerca una sottostringa
testo.indexOf("Script"); // 4 - Dove inizia (-1 se non trova)
// Pulizia e sostituzione
" spazi ovunque ".trim(); // "spazi ovunque"
testo.replace("potente", "fantastico"); // Sostituisce la *prima* occorrenza
testo.replaceAll("e", "3"); // Sostituisce *tutte* le occorrenze
Il Metodo .concat() vs Template Literals
Cosa fa: Unisce due stringhe.
const base = "https://sito.com/";
const path = "foto.jpg";
// 1. Orientato agli Oggetti (Il Verbo) 🐢
// "Ehi base, concatena a te stesso path"
const url1 = base.concat(path);
// 2. Matematico (Intuitivo) ➕
const url2 = base + path;
// 3. Moderno (Il Vincitore) 🏆
const url3 = `${base}${path}`;
Perché .concat() esiste?: Segue la logica Soggetto (base) -> Verbo (.concat) -> Oggetto (path).
Consiglio: Imparalo per i test, ma nella vita reale usa i Template Literals (opzione 3). Sono più leggibili e potenti.
Il Metodo .split() - L'Affettatrice di Stringhe
.split() è come un coltello magico che taglia una stringa nei punti che decidi tu. Ma la magia vera è che trasforma una stringa in un array: passa da un blocco unico a una lista di pezzi manipolabili singolarmente.
"Ciao mondo felice".split(' '); // ['Ciao', 'mondo', 'felice']
"2025-01-15".split('-'); // ['2025', '01', '15']
"hello".split(""); // ["h", "e", "l", "l", "o"] - Ogni lettera!
Il separatore che scegli è come decidere dove tagliare una torta.
.charCodeAt() vs .codePointAt() (Gestione Unicode/Emoji)
.charCodeAt() è il traduttore "classico" da carattere a numero (il suo codice Unicode). Ma è vecchio e si confonde con le emoji! 😵
Pensa a .charCodeAt() come a un traduttore che non capisce le parole composte. Le emoji (e alcuni caratteri rari) sono spesso composte da due "pezzi" di codice (surrogate pairs). .charCodeAt() vede solo i pezzi singoli e ti dà due numeri strani e inutili.
"A".charCodeAt(0); // 65
"🎉".charCodeAt(0); // 55357 (sbagliato!)
"🎉".charCodeAt(1); // 56894 (l'altro pezzo)
.codePointAt() è il traduttore moderno. È più intelligente: capisce le coppie surrogate e ti dà il loro vero, unico codice numerico.
"A".codePointAt(0); // 65
"🎉".codePointAt(0); // 127881 (Corretto!)
Regola: Impara .charCodeAt(), ma usa sempre .codePointAt() nel codice moderno per evitare problemi con emoji e caratteri speciali.
String.fromCharCode() vs String.fromCodePoint()
Questa è l'operazione inversa: da numero a carattere.
String.fromCharCode() è il traduttore "classico" (numero ➡️ carattere). Come charCodeAt(), non capisce i codici alti delle emoji.
String.fromCharCode(65); // "A"
// String.fromCharCode(127881); // ERRORE, non funziona o dà caratteri strani
String.fromCodePoint() è il traduttore moderno. Dagli il codice giusto e lui ti darà l'emoji. È un metodo statico, quindi si chiama su String (maiuscolo).
String.fromCodePoint(65); // "A"
String.fromCodePoint(127881); // "🎉" (Corretto!)
Regola: Impara fromCharCode(), ma usa sempre String.fromCodePoint().
.startsWith() (Controllo Inizio Stringa)
Questo metodo moderno (ES6) controlla se una stringa inizia con un'altra stringa. È preferito perché comunica l'intento (cosa vuoi fare) invece dei passi (come farlo).
const file = "documento.pdf";
// MODO MODERNO (chiaro, leggibile: "La stringa inizia con...?")
file.startsWith("documento"); // true
// MODO CLASSICO (meccanico: "Prendi il primo carattere...")
file.charAt(0) === 'd'; // true, ma meno chiaro e robusto
file.slice(0, 9) === "documento"; // Funziona, ma verboso
Numeri (Number) - I Valori Matematici
I numeri in JavaScript sono ingannevolmente semplici. Non c'è distinzione tra interi e decimali - tutto è un Number. Ma questa semplicità nasconde alcune stranezze fondamentali, come un pavimento lucido che ha qualche mattonella scivolosa.
Tipi di numeri e Valori speciali (Infinity, NaN)
const intero = 42;
const decimale = 3.14;
const esponenziale = 5.2e3; // 5200 (notazione scientifica)
const infinito = Infinity;
const nonNumero = NaN; // Not a Number
NaN è un valore subdolo: è l'unico valore in JavaScript che non è uguale a se stesso (NaN === NaN è false!). Per questo servono funzioni apposite per controllarlo.
Conversioni e Controlli
Qui è dove le cose si fanno interessanti. Hai diversi strumenti per convertire e controllare i numeri, ognuno con un lavoro diverso.
-
isNaN()(La Spiegazione Approfondita)Pensa a
isNaN()(quella globale) come a un doganiere un po' confuso. Il suo lavoro dovrebbe essere controllare se un valore èNaN, ma prima di farlo prova a convertirlo forzatamente in un numero!isNaN(NaN); // true (Ovvio)
isNaN("Ciao"); // true (Perché? Prova Number("Ciao") -> NaN. Doganiere: "Sì, è NaN!")
isNaN("123"); // false (Perché? Prova Number("123") -> 123. Doganiere: "No, è 123")
isNaN(undefined); // true (Perché? Number(undefined) -> NaN)
// IL TRABOCCHETTO!
isNaN(null); // false (Perché? Number(null) -> 0. Doganiere: "No, è 0")È un controllo inaffidabile. Per un controllo moderno e rigoroso se un valore è esattamente il tipo
Numbere il valoreNaN, usaNumber.isNaN():Number.isNaN(NaN); // true
Number.isNaN("Ciao"); // false (Non è *già* NaN, è una stringa!) -
Number()(Conversione rigorosa)Number()è un traduttore "tutto o niente". Tenta di convertire l'intero valore. Se fallisce, restituisceNaN. È il più rigoroso e prevedibile.Number("123"); // 123
Number("3.14"); // 3.14
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(""); // 0 (Attenzione!)
// Rigoroso: fallisce se c'è testo
Number("42px"); // NaN
Number("Ciao"); // NaN -
parseInt()eparseFloat()(Conversioni tolleranti)Questi sono "estrattori". Sono come netturbini che leggono da sinistra a destra e prendono solo i numeri che trovano all'inizio, buttando via il resto.
parseInt()(Solo Interi):parseInt("42.5px"); // 42 (Estrae 42, vede "." e si ferma)
parseInt("age 42"); // NaN (Inizia con testo, fallisce subito)Best Practice: Usa sempre il secondo argomento (la "base" o radix) per dire a
parseIntche stai lavorando in base 10 (il nostro sistema decimale).parseInt("10", 10); // 10
parseInt("10", 2); // 2 (interpreta "10" come binario)parseFloat()(Con Decimali):parseFloat("42.5px"); // 42.5 (Estrae 42.5, vede "p" e si ferma)
parseFloat("3.14.15"); // 3.14 (Vede il secondo "." e si ferma)
Math - La Calcolatrice Scientifica
L'oggetto Math è come avere una calcolatrice scientifica sempre a disposizione, ma integrata nel linguaggio. È un oggetto statico, non devi mai crearlo (new Math() non esiste).
-
Math.floor(),Math.ceil(),Math.round()Math.floor(4.9): 4 (Pensa a "floor" - pavimento. Arrotonda sempre giù all'intero inferiore).Math.ceil(4.1): 5 (Pensa a "ceiling" - soffitto. Arrotonda sempre su all'intero superiore).Math.round(4.5): 5 (Arrotonda al più vicino, come a scuola.4.4->4,4.5->5).
-
Math.random()(Generatore di Casualità)Math.random()genera un numero pseudo-casuale tra 0 (incluso) e 1 (escluso). È come lanciare un dado con infinite facce microscopiche. Da solo non è molto utile, ma è la base per tutto.// Formula generale: intero tra min e max (inclusi)
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
randomInt(1, 6); // Un numero casuale tra 1 e 6 -
Math.pow()vs Operatore**Entrambi fanno l'elevamento a potenza, ma
**è la scorciatoia moderna (ES6+).// Modo classico
Math.pow(2, 3); // 8 (2 alla terza)
// Modo moderno (preferito)
2 ** 3; // 8 -
Math.sqrt()(Leggibilità e Intento)Per la radice quadrata, hai tre opzioni.
Math.sqrt()è la migliore perché comunica l'intento. Il codice non deve solo funzionare, deve anche spiegare cosa fa.// 1. Matematicamente corretto, ma "difficile" da leggere
Math.pow(9, 0.5); // 3
// 2. Moderno, ma richiede di "sapere" che ** 0.5 è la radice
9 ** 0.5; // 3
// 3. Il migliore: chiaro, leggibile, auto-esplicativo
Math.sqrt(9); // 3 (sqrt = SQuare RooT)Scrivi codice che si spiega da solo: usa
Math.sqrt()per le radici quadrate.
Gestione Decimali (Floating Point)
-
Il Problema (IEEE-754) I computer "sbagliano" i calcoli con i decimali. Prova a scrivere
0.1 + 0.2nella console: non fa0.3, ma0.30000000000000004. Perché? I computer pensano in binario (base 2). Alcuni numeri semplici in base 10 (come 0.1, cioè 1/10) sono numeri infiniti e periodici in binario (per lo stesso motivo per cui 1/3 è 0.333... in base 10). Il computer deve "tagliarli", introducendo piccoli errori di precisione. -
.toFixed()(Arrotondamento a Stringa) La soluzione per la visualizzazione è.toFixed(). Arrotonda il numero ancifre decimali. Attenzione: Restituisce una STRINGA, non un numero! È fatto per mostrare il valore all'utente, non per farci altri calcoli.const risultato = 0.1 + 0.2; // 0.30000000000000004
const visualizza = risultato.toFixed(2); // "0.30" (una stringa!) -
parseFloat()(Riconversione a Numero) Se devi usare quel numero arrotondato in altri calcoli (come per le valute), devi riconvertirlo da stringa a numero. Questo è un pattern fondamentale.const subTotal = 100.50;
const taxRate = 0.0825;
// Calcola, arrotonda a stringa, riconverti a numero
const taxes = parseFloat((subTotal * taxRate).toFixed(2)); // 8.30 (un numero!)
const total = subTotal + taxes; // 108.80
3. Date - Il Calendario e l'Orologio
Le date in JavaScript sono oggetti complessi che rappresentano un momento preciso nel tempo, misurato in millisecondi dal 1 gennaio 1970 00:00:00 UTC (l'Unix Epoch). Sono notoriamente difficili da gestire.
Creare date
const ora = new Date(); // Data e ora correnti
const compleanno = new Date(2025, 0, 15); // 15 gennaio 2025 (mese 0!)
const daStringa = new Date("2025-01-15T10:00:00"); // Da stringa ISO
I Metodi Tricky (getMonth 0-indexed)
Attenzione! Le date sono piene di trabocchetti storici ereditati da altri linguaggi.
- IL TRABOCCHETTO PEGGIORE:
getMonth()restituisce il mese da 0 a 11. (Gennaio è 0, Dicembre è 11). getDay()restituisce il giorno della settimana da 0 a 6. (Domenica è 0, Sabato è 6).getDate()restituisce il giorno del mese da 1 a 31 (questo è normale, per fortuna).
const oggi = new Date(2025, 11, 25); // 25 Dicembre 2025
oggi.getMonth(); // 11 (Dicembre)
oggi.getDate(); // 25
oggi.getDay(); // 4 (Giovedì)
È una fonte inesauribile di bug. Ricordalo sempre!
Date.now() (Timestamp)
Date.now() è geniale nella sua semplicità. Non crea un oggetto, restituisce solo un numero: i millisecondi totali passati dal 1970. È perfetto per misurare il tempo, creare ID unici, o gestire scadenze.
const start = Date.now();
// ... codice pesante da misurare ...
const end = Date.now();
console.log(`Operazione durata: ${end - start}ms`);
4. Booleani (Boolean) - Il Sistema Binario della Logica
I booleani sono i bit filosofici di JavaScript. Solo due valori: true o false. Sono il cuore di ogni decisione (if, while, ternario) che il tuo programma prende. Sono come gli interruttori della luce: acceso o spento, sì o no, procedi o fermati.
Truthy vs Falsy - La Zona Grigia della Verità
JavaScript ha questa caratteristica affascinante e a volte frustrante: in un contesto booleano, OGNI valore viene "costretto" a diventare true o false. È come se JavaScript avesse degli occhiali speciali che vedono tutto solo in bianco e nero.
I Sei Cavalieri del Falsy (memorizzali! Questi sono gli UNICI valori "falsi"):
false0(zero numerico)""(stringa vuota)nullundefinedNaN
TUTTO il resto è truthy! Anche cose controintuitive:
"0"(true - è una stringa con contenuto!)"false"(true - è una stringa con testo!)[](true - un array vuoto è un oggetto e gli oggetti sono truthy!){}(true - un oggetto vuoto esiste!)
Questo ti permette di scrivere controlli molto concisi:
const username = ""; // Falsy
if (!username) { // !username è true
console.log("Per favore, inserisci un nome!");
}
5. Array (Array) - Le Liste Ordinate
Gli array sono le collezioni ordinate di JavaScript. Pensa a loro come treni con vagoni: ogni vagone (elemento) ha un numero (indice), puoi aggiungere o rimuovere vagoni, riordinarli, o trasformare l'intero treno.
Creazione ([] vs Array() (Costruttore))
[](Sintassi Letterale - Preferita): È il modo standard.const arr = [1, 2, 3];Array()(Costruttore): Ha un comportamento "strano" e utile.Array(1, 2, 3): Crea[1, 2, 3].Array(3): NON crea[3]. Crea[ <3 empty items> ](un array vuoto con 3 posti vuoti, come una scatola per uova vuota).
Accesso (Indice da 0)
L'informatica conta da zero. Il primo elemento è sempre all'indice 0.
const frutti = ["mela", "pera", "banana"];
frutti[0] è "mela". frutti[2] è "banana".
Proprietà .length
frutti.length è 3. È una proprietà (senza parentesi ()) che indica il numero di elementi.
- L'ultimo elemento è sempre a
frutti.length - 1. - È modificabile:
frutti.length = 0svuota l'array!
Set e Proprietà .size (Per valori unici)
Un Set è una struttura dati correlata, un "club VIP" che accetta solo valori unici.
new Set([1, 1, 2, 3, 3]) -> Set { 1, 2, 3 }
Per contare gli elementi unici, si usa la proprietà .size (non .length).
const numeri = [1, 1, 2, 3];
numeri.length; // 4
new Set(numeri).size; // 3
Metodi di Modifica (Distruttivi)
Questi metodi sono come operazioni chirurgiche: modificano l'array originale. Usali con cautela.
-
.push(el): Aggiunge alla fine. -
.pop(): Rimuove dalla fine. -
.unshift(el): Aggiunge all'inizio (è un'operazione lenta per array grandi!). -
.shift(): Rimuove dall'inizio (anch'essa lenta). -
.splice()(Il Coltellino Svizzero) È il metodo più potente e complesso. Può rimuovere, aggiungere o sostituire.array.splice(indiceInizio, quantiDaRimuovere, ...elementiDaAggiungere)const lettere = ['a', 'b', 'c', 'd'];
// Sostituiamo 2 elementi ('b', 'c') a partire dall'indice 1 con 'X'
lettere.splice(1, 2, 'X');
// lettere ora è: ['a', 'X', 'd'] -
.sort()(Attenzione: modifica originale, default alfabetico) La trappola più famosa!.sort()ordina alfabeticamente (come stringhe) di default.const numeri = [10, 2, 5];
numeri.sort(); // [10, 2, 5] (sbagliato! "10" viene prima di "2")
// La soluzione: la funzione di confronto
numeri.sort((a, b) => a - b); // [2, 5, 10] (Corretto, crescente)
numeri.sort((a, b) => b - a); // [10, 5, 2] (Decrescente)
Metodi di Lettura (Non Distruttivi)
Questi metodi sono "gentili": creano un nuovo array senza toccare l'originale. Sono fondamentali per la programmazione funzionale e l'immutabilità (un pattern che vedremo).
-
.slice()(Creare copie).slice()è la "fotocopiatrice" degli array.const numeri = [1, 2, 3, 4, 5];
const copia = numeri.slice(); // Fotocopia l'intero array
const primiDue = numeri.slice(0, 2); // [1, 2] (indice 2 escluso)
// Pattern per ordinare senza distruggere:
const ordinati = numeri.slice().sort((a, b) => a - b); -
.filter(fn): Il "setaccio". Crea un nuovo array solo con gli elementi che passano il test.numeri.filter(n => n > 2); // [3, 4, 5] -
.find(fn): Il "detective". Restituisce il primo elemento che matcha la condizione (oundefined).numeri.find(n => n > 2); // 3 -
.findIndex(fn): Restituisce l'indice del primo elemento che matcha (o-1).numeri.findIndex(n => n > 2); // 2 -
.includes(val): Controllo rapido: "C'è questo valore?". Restituiscetrueofalse. -
.indexOf(val): Dov'è questo valore? Restituisce l'indice (o-1se non trovato). -
.join(sep): L'"incollatore". Unisce un array in una stringa, usando un separatore.["a", "b", "c"].join("-"); // "a-b-c"
Metodi Funzionali (Iterazione/Trasformazione)
-
.map(fn)(Trasformazione) La "fabbrica di trasformazione". Prende un array, applica una funzione a ogni elemento e restituisce un nuovo array della stessa lunghezza con i risultati.const numeri = [1, 2, 3];
const doppi = numeri.map(n => n * 2); // [2, 4, 6] -
.reduce(fn, valIniziale)(Accumulazione) Il "caldaia" o "robot da cucina". Fa bollire un intero array per produrre un singolo valore (una somma, un oggetto, una stringa...).const numeri = [1, 2, 3];
// (acc = accumulatore, curr = valore corrente)
const somma = numeri.reduce((acc, curr) => acc + curr, 0); // 6Il
, 0è ilvaloreIniziale. È una best practice fondamentale fornirlo sempre, altrimentireduceusa il primo elemento come valore iniziale e salta la prima iterazione, causando bug con array vuoti. -
.some(fn)(Almeno uno) Controlla se almeno un elemento passa il test. È super efficiente: si ferma al primotrueche trova.numeri.some(n => n > 2); // true -
.every(fn)(Tutti) Controlla se tutti gli elementi passano il test. Si ferma al primofalseche trova.numeri.every(n => n > 0); // true -
.fill(val)(Riempimento, ponte per.map()) Come abbiamo visto,Array(3)crea[ <3 empty items> ](posti vuoti)..map()ignora i posti vuoti!.fill()è il "ponte" che trasforma i posti vuoti in posti pieni (es.[undefined, undefined, undefined]), rendendo.map()utilizzabile.
Pattern e Logica con Array
-
Pattern: Creare un Range di Numeri Questo pattern unisce
Array(N),.fill()e.map().const range = (start, end) => {
const lunghezza = end - start + 1;
// 1. Crea posti vuoti
// 2. .fill() li rende "mappabili" (riempiendoli con undefined)
// 3. .map() usa l'indice per creare la sequenza
return Array(lunghezza).fill().map((_, index) => start + index);
};
range(1, 5); // [1, 2, 3, 4, 5] -
Logica: Trovare la Mediana (Dispari e Pari) (Richiede un array già ordinato!)
const arrOrdinatoPari = [1, 2, 3, 4, 5, 6]; // Lunghezza 6
const arrOrdinatoDispari = [1, 2, 3, 4, 5]; // Lunghezza 5
// Caso Dispari (lunghezza 5)
const indiceDispari = Math.floor(arrOrdinatoDispari.length / 2); // floor(2.5) -> 2
const medianaDispari = arrOrdinatoDispari[indiceDispari]; // 3
// Caso Pari (lunghezza 6)
const centroDx = arrOrdinatoPari.length / 2; // 3
const centroSx = centroDx - 1; // 2
const el1 = arrOrdinatoPari[centroSx]; // 3
const el2 = arrOrdinatoPari[centroDx]; // 4
const medianaPari = (el1 + el2) / 2; // 3.5
6. Oggetti (Object) - I Contenitori Strutturati
Gli oggetti sono il cuore di JavaScript. Se gli array sono "liste ordinate", gli oggetti sono "collezioni non ordinate" di coppie chiave-valore. Sono come un dizionario o una rubrica telefonica dove ogni informazione ha un'etichetta (la chiave).
Creazione e Oggetti annidati (nested)
Gli oggetti possono contenere altri oggetti. È come avere scatole dentro altre scatole.
const utente = {
nome: "Mario",
email: "mario@rossi.it",
indirizzo: { // Oggetto annidato
citta: "Roma",
cap: "00100"
},
// Pattern comune per raggruppare stati
keys: {
rightKey: { pressed: false },
leftKey: { pressed: false }
}
};
Questo è fondamentale per l'organizzazione. Invece di avere variabili sparse come utenteCitta, utenteCAP, utenteRightKeyPressed, raggruppi tutto logicamente.
Accesso (Notazione a Punto, Parentesi, Optional Chaining ?.)
-
Notazione a Punto (
.): La più comune, pulita e veloce.utente.nome; // "Mario"utente.indirizzo.citta; // "Roma" -
Notazione a Parentesi (
[]): Obbligatoria in due casi:- La chiave è una variabile:
const chiave = "nome"; utente[chiave]; // "Mario" - La chiave ha caratteri speciali:
utente["data-di-nascita"] = "..."
- La chiave è una variabile:
-
Optional Chaining (
?.): Il salvavita (ES2020)! Impedisce errori se un oggetto intermedio non esiste.// Senza: ERRORE se `utente.lavoro` non esiste
// const stipendio = utente.lavoro.stipendio; // Crash!
// Con: Sicuro
const stipendio = utente.lavoro?.stipendio; // undefined (nessun crash!)
Shorthand Property Names (ES6)
Una scorciatoia sintattica comodissima. Se il nome della chiave che vuoi creare è identico al nome della variabile che contiene il valore, puoi scriverlo una volta sola.
const nome = "Mario";
const eta = 30;
// Classico:
const utenteClassico = { nome: nome, eta: eta };
// Moderno (Shorthand):
const utenteModerno = { nome, eta }; // Fa la stessa identica cosa!
Destrutturazione (ES6) - Le Matrioske 🪆
Cosa fa: L'operazione inversa della creazione: estrae valori da un oggetto e li "spacchetta" in variabili separate in modo chirurgico e pulito.
const prodotto = { id: 1, nome: "Libro", prezzo: 15 };
// Vecchio modo (Verboso):
const nomeVec = prodotto.nome;
const prezzoVec = prodotto.prezzo;
// Moderno (Destructuring):
const { nome, prezzo } = prodotto;
// Ora hai due nuove variabili pronte: nome ("Libro") e prezzo (15)
Avanzato (Nesting):
const data = {
topic_list: {
topics: ['Post 1', 'Post 2'],
more_topics_url: '...'
},
users: [...]
};
// Vecchio modo (Noioso):
const topics = data.topic_list.topics;
// Modo Matrioska (Elegante):
// Nota la sintassi: uso i ':' per scendere di livello
const { topic_list: { topics } } = data;
Analogia: Invece di tirare fuori la scatola grande, aprirla, tirare fuori la media, aprirla... con la destrutturazione ti teletrasporti direttamente alla bambolina più piccola!
Best Practice: Il Renaming (CamelCase vs Snake_case) 🐫 vs 🐍
Le API spesso parlano in snake_case (es. topic_list), ma JS ama il camelCase.
Risolvi il problema direttamente mentre destrutturi:
// Leggi 'topic_list' dall'oggetto, ma crea una variabile chiamata 'topicList'
const { topic_list: topicList } = data;
È come avere un traduttore simultaneo mentre apri la scatola!
Metodi Statici (Object.keys(), Object.values(), Object.entries())
Questi sono strumenti per trasformare un oggetto (che non puoi ciclare facilmente con map o filter) in un array (che puoi!).
Object.keys(utente):["nome", "email", "indirizzo", "keys"](Un array delle chiavi)Object.values(utente):["Mario", "mario@rossi.it", {...}, {...}](Un array dei valori)Object.entries(utente):[["nome", "Mario"], ["email", "mario@rossi.it"], ...](Un array di coppie[chiave, valore])
// Uso pratico:
const prezzi = { mela: 1, pera: 2, banana: 1.5 };
// Aumentiamo tutti i prezzi del 10%
Object.entries(prezzi).forEach(([frutto, prezzo]) => {
prezzi[frutto] = prezzo * 1.10;
});
hasOwnProperty() vs Object.hasOwn() (Moderno)
hasOwnProperty controlla se una proprietà appartiene direttamente all'oggetto (non ereditata dal prototype). È come controllare se una stanza è sul tuo rogito di casa o se è una parte comune del condominio.
const utente = { nome: "Mario" };
utente.hasOwnProperty("nome"); // true
utente.hasOwnProperty("toString"); // false (è ereditata!)
// Moderno (preferito):
Object.hasOwn(utente, "nome"); // true
Usa Object.hasOwn() perché è un metodo statico e previene rari errori in cui un oggetto potrebbe essere stato creato senza ereditare hasOwnProperty (es. Object.create(null)).
Pattern: Mappa di Frequenza (Contatore)
Un uso comune degli oggetti è contare le occorrenze, creando una "mappa di frequenza".
const voti = ["A", "B", "A", "A", "C", "B"];
const conteggio = {};
voti.forEach(voto => {
// La magia è qui: (conteggio[voto] || 0)
// Se conteggio[voto] esiste, usa il suo valore
// Altrimenti (è undefined, falsy), usa 0
// Poi aggiungi 1
conteggio[voto] = (conteggio[voto] || 0) + 1;
});
// conteggio ora è: { A: 3, B: 2, C: 1 }
7. Operatori Logici e Sintassi
Se le variabili sono i "contenitori" e i tipi di dati sono la "forma" dell'informazione, gli operatori sono gli ingranaggi e la colla del tuo programma. Sono i verbi che ti permettono di combinare, confrontare, trasformare e prendere decisioni.
Operatore || (OR) per Valori di Default
Questo è uno degli operatori più fraintesi ma più utili. Molti pensano che || (OR) restituisca solo true o false, ma in JavaScript è molto più potente: è un selettore di valori.
La sua logica è: "restituisci il primo valore truthy che incontri".
Pensa a || come a un "Piano B". JavaScript controlla il primo valore. Se è "abbastanza buono" (truthy), lo restituisce. Se è "inutile" (falsy), allora e solo allora, restituisce il secondo valore come fallback.
Ricorda i Sei Cavalieri del Falsy (gli unici valori "inutili"):
false0""(stringa vuota)nullundefinedNaN
Tutto il resto è truthy (inclusi [] e {}).
// Esempio 1: Fornire un fallback
const nomeUtente = "" || "Ospite";
// JavaScript vede "" (falsy), quindi sceglie il "Piano B"
// nomeUtente è "Ospite"
const nomeUtenteReale = "Mario" || "Ospite";
// JavaScript vede "Mario" (truthy), lo prende subito
// nomeUtenteReale è "Mario"
// Esempio 2: Il Pattern del Contatore (FONDAMENTALE)
// Immagina di contare le parole in un testo
const conteggi = {};
const parola = "ciao";
// Prima volta che incontriamo "ciao":
// conteggi[parola] è undefined (falsy)
// Quindi (undefined || 0) diventa 0
// E 0 + 1 fa 1
conteggi[parola] = (conteggi[parola] || 0) + 1;
// conteggi ora è { ciao: 1 }
// Seconda volta che incontriamo "ciao":
// conteggi[parola] è 1 (truthy)
// Quindi (1 || 0) diventa 1
// E 1 + 1 fa 2
conteggi[parola] = (conteggi[parola] || 0) + 1;
// conteggi ora è { ciao: 2 }
Alternativa Moderna (??): Attenzione! A volte 0 o "" sono valori validi che non vuoi scartare. In quel caso, usa il "Nullish Coalescing Operator" (??), che scatta solo per null o undefined.
Operatore ! (NOT) e Pattern "Toggle"
L'operatore ! (NOT) è l'interruttore della luce della logica. Inverte un valore booleano.
!truediventafalse!falsediventatrue
Come ||, lavora con i valori truthy/falsy. Prima "costringe" qualsiasi valore a diventare true o false, e poi lo inverte.
!true; // false
!false; // true
!"Pizza"; // "Pizza" è truthy, quindi Boolean("Pizza") è true. !true è false.
!""; // "" è falsy. Boolean("") è false. !false è true.
!0; // 0 è falsy. Boolean(0) è false. !false è true.
!null; // null è falsy. Boolean(null) è false. !false è true.
![]; // [] è truthy. Boolean([]) è true. !true è false.
Il Pattern "Toggle" (Interruttore)
Questo è l'uso più elegante di !. Ti permette di invertire uno stato booleano in una singola, leggibilissima riga.
let isMenuOpen = false; // Il menu è chiuso
function toggleMenu() {
isMenuOpen = !isMenuOpen;
// 1° click: isMenuOpen = !false -> isMenuOpen diventa true
// 2° click: isMenuOpen = !true -> isMenuOpen diventa false
// 3° click: isMenuOpen = !false -> isMenuOpen diventa true
// ...e così via, come un interruttore della luce.
}
Il trucco del Doppio NOT (!!)
A volte vedi !!valore. Non è un errore, è un trucco! È il modo più veloce per convertire qualsiasi valore nel suo equivalente booleano puro (truthy/falsy).
!!5; // true
!!""; // false
!!{}; // true
Operatore Spread (...)
L'operatore spread (o "di diffusione") è magia pura. Pensa a un array o a un oggetto come a uno scatolone chiuso. I tre puntini ... sono l'atto di aprire lo scatolone e svuotarne il contenuto sul tavolo, pezzo per pezzo.
Con gli Array (Liste)
-
Creare Copie (Immutabilità):
const originale = ["a", "b", "c"];
// const copiaSbagliata = originale; // ERRORE! Questa è solo un'altra etichetta per lo stesso scatolone!
const copiaCorretta = [...originale]; // Prendi uno scatolone nuovo e svuotaci dentro i pezzi dell'originale
copiaCorretta.push("d");
// originale è ancora ["a", "b", "c"] -
Unire (Concatenare):
const arr1 = [1, 2];
const arr2 = [3, 4];
const uniti = [...arr1, 5, ...arr2]; // [1, 2, 5, 3, 4] -
Passare Argomenti a Funzioni: Alcune funzioni (come
Math.max) non accettano uno scatolone (array), vogliono i pezzi singoli.const numeri = [10, 5, 20];
// Math.max(numeri); // ERRORE: NaN
Math.max(...numeri); // Corretto! È come scrivere Math.max(10, 5, 20)
Con gli Oggetti (Dizionari)
- Creare Copie e Aggiornare (Immutabilità):
const utente = { nome: "Mario", eta: 30 };
// Copia e aggiorna l'età
const utenteAggiornato = { ...utente, eta: 31 };
// { nome: "Mario", eta: 31 }
// L'originale è intatto!
// L'ordine conta!
const utenteConflitto = { ...utente, nome: "Luigi" }; // { nome: "Luigi", eta: 30 }
const utenteConflitto2 = { nome: "Luigi", ...utente }; // { nome: "Mario", eta: 30 }
Distinzione: Spread vs. Rest
Attento a non confondere "Spread" (che espande) con "Rest" (che raccoglie). La sintassi è la stessa (...), ma il contesto è opposto.
...in una chiamata o definizione di array/oggetto = Spread (espandi)...nei parametri di una funzione = Rest (raccogli)function(...args) { /* args è un array di tutti gli argomenti */ }
Operatore di Esponenziazione (**)
Questa è una scorciatoia moderna (ES6+) per l'elevamento a potenza. È il "tasto rapido" sulla calcolatrice.
Prima, per fare $2^3$ (2 alla terza), dovevi usare la "calcolatrice scientifica" Math:
// Modo classico
Math.pow(2, 3); // 8
Math.pow(9, 0.5); // 3 (per la radice quadrata)
Ora, puoi usare ** che è più pulito, leggibile e si integra con gli altri operatori matematici.
// Modo moderno (preferito)
2 ** 3; // 8
9 ** 0.5; // 3
Assegnazione a Catena
È una sintassi che vedi a volte per inizializzare più variabili allo stesso valore.
let a, b, c;
a = b = c = "valore";
console.log(a); // "valore"
console.log(b); // "valore"
console.log(c); // "valore"
Come funziona? (Il "Gotcha") L'assegnazione in JavaScript viene valutata da destra a sinistra e l'intera operazione di assegnazione restituisce il valore assegnato.
"valore"viene assegnato ac.- L'espressione
c = "valore"restituisce il valore"valore". - Questo valore (
"valore") viene assegnato ab. - L'espressione
b = "valore"restituisce"valore". - Questo valore (
"valore") viene assegnato aa.
L'Errore Comune (Sintassi Sbagliata) Non puoi usare operatori logici per assegnazioni multiple.
// ERRORE DI SINTASSI!
// a && b = "valore"; // Sbagliato!
Questo non funziona perché il lato sinistro di un'assegnazione (=) deve essere un riferimento valido (come un nome di variabile, es. a), non un'espressione booleana (a && b).
Best Practice: Evita l'assegnazione a catena. È sinteticamente carina, ma può essere pessima per la leggibilità e il debugging. Scrivere le assegnazioni su righe separate è quasi sempre meglio, più chiaro e più facile da manutenere.
// Meglio così:
let a = "valore";
let b = "valore";
let c = "valore";
Input/Output e Strutture di Controllo
8. Output e Commenti
Comunicare è fondamentale, non solo con l'utente, ma anche con te stesso e con gli altri sviluppatori. Il tuo codice deve "parlare". L'output tramite console è il tuo megafono durante lo sviluppo, mentre i commenti sono i tuoi appunti a margine, essenziali per la comprensione a lungo termine.
console - La Cabina di Controllo
Pensa alla console come alla cabina di pilotaggio del tuo programma. Non è un output per i tuoi passeggeri (gli utenti) - per quello userai il DOM (vedi Parte II). È il pannello di controllo per te, il pilota (lo sviluppatore). È il posto dove il motore ti dice se sta funzionando bene, dove controlli i valori al volo e dove diagnostichi i problemi.
Il metodo console.log() è il tuo strumento principale. È come un "walkie-talkie" che ti permette di inviare un messaggio da qualsiasi punto del tuo codice alla cabina di controllo.
// Puoi passare qualsiasi cosa:
console.log("Il programma è partito!");
console.log("Valore del contatore:", contatore); // Passa più argomenti
console.log(utente); // Ispeziona un intero oggetto!
console.log(unArrayDiDati); // Ispeziona un array
Ma la cabina di controllo è molto più sofisticata di un singolo log. Ha un intero pannello di strumenti specializzati:
// STRUMENTI DI DEBUG AVANZATI
// 1. Log Semantici (per colore e contesto)
console.log("Messaggio normale");
console.info("Informazione utile"); // Spesso con un'icona (i)
console.warn("Attenzione!"); // Giallo ⚠️
console.error("Errore critico!"); // Rosso ❌ (blocca l'esecuzione se è un errore vero)
// 2. Ispezione Dati (fondamentale!)
const utenti = [
{ id: 1, nome: "Mario", eta: 30 },
{ id: 2, nome: "Luigi", eta: 28 }
];
console.table(utenti); // Mostra una tabella interattiva e ordinabile!
// 3. Organizzazione Output
console.group("Inizio Validazione Utente"); // Inizia un gruppo collassabile
console.log("Controllo nome...");
console.warn("Email mancante");
console.groupEnd(); // Chiude il gruppo
// 4. Misurazione Performance
console.time("timerLoop"); // Avvia un cronometro chiamato "timerLoop"
for (let i = 0; i < 1000; i++) {
// ...
}
console.timeEnd("timerLoop"); // Ferma il cronometro e stampa il tempo trascorso
// 5. Conteggio
function bottoneCliccato() {
console.count("clickBottone"); // Stampa: "clickBottone: 1", "clickBottone: 2", ...
}
Usare console è l'arte del testing incrementale. Invece di scrivere 100 righe di codice e sperare che funzionino, ne scrivi 5 e usi console.log() per "assaggiare" il risultato, proprio come uno chef assaggia il sugo mentre cucina.
Commenti - La Documentazione del Codice
I commenti sono post-it nel tuo codice. Sono messaggi per il "te stesso del futuro" o per i tuoi colleghi. Il codice ti dice come fa qualcosa, ma i commenti devono spiegare perché lo fa.
// Commento singola linea - per note brevi
const tasse = prezzo * 0.22; // Applica IVA al 22%
/* Commento multi-linea
Usato per spiegazioni più lunghe, per descrivere
la logica complessa di una funzione, o per
disabilitare temporaneamente un blocco di codice
senza cancellarlo.
*/
/*
function vecchiaFunzione() {
console.log("Questa non serve più");
}
*/
JSDoc - La Documentazione Formale
Quando scrivi una funzione o una classe, usare il formato /** ... */ (JSDoc) è una best practice professionale. Non è solo un commento, è documentazione che il tuo editor (e altri strumenti) possono leggere per darti suggerimenti automatici.
/**
* JSDoc - Documentazione formale
* @param {number} prezzo - Il prezzo base dell'articolo
* @param {number} sconto - La percentuale di sconto (es. 20)
* @returns {number} Il prezzo finale scontato
*/
function applicaSconto(prezzo, sconto) {
return prezzo * (1 - sconto / 100);
}
Tag Speciali - Organizzare il Lavoro
Usa tag standard per creare una "lista di cose da fare" interna al codice.
// TODO: Implementare la validazione dell'email con una regex
// FIXME: Questo non gestisce i numeri negativi, crasha
// NOTE: L'API richiede il formato data ISO (AAAA-MM-GG)
// HACK: Aggiunto un piccolo timeout per aspettare l'animazione CSS (400ms)
// DEPRECATED: Usare la nuova funzione `calcolaTotaleV2()` dalla v2.0
Best Practices per Commenti: Spiega il "Perché", non il "Cosa"
I commenti non devono essere un'eco del codice. Devono aggiungere valore.
// CATTIVO: Commento ovvio e inutile
let count = 0; // Imposta count a 0
// BUONO: Spiega il "perché" e il contesto
let count = 0; // Contatore tentativi falliti (max 3 prima del blocco account)
Un buon commento è come una nota a margine su un libro difficile: non ripete il testo, ma ti dà la chiave di lettura per capirlo.
9. Controllo del Flusso - Le Decisioni del Programma
Se il codice fosse una ricetta, finora abbiamo visto solo gli ingredienti (i tipi di dato) e gli utensili (gli operatori). Il controllo del flusso è la ricetta stessa: è la sequenza di passaggi, le decisioni, i "se" e gli "altrimenti" che trasformano una lista statica di istruzioni in un programma dinamico e intelligente. È il punto in cui il tuo codice smette di essere un sasso e inizia a essere un robot.
if/else - Il Bivio Classico
Pensa a if come a un bivio sulla strada. Il tuo programma arriva al bivio e deve prendere una decisione. La condizione tra parentesi () è il cartello stradale che il programma legge.
// La condizione è la domanda
if (condizione) {
// ...blocco di codice se la condizione è 'true'
}
La parte cruciale da capire è che la condizione viene sempre costretta a diventare un booleano. Qui è dove i concetti di Truthy e Falsy diventano fondamentali.
const username = "Mario";
if (username) { // "Mario" è truthy, quindi il codice entra
console.log(`Benvenuto, ${username}`);
}
const punteggio = 0;
if (punteggio) { // 0 è falsy, quindi il codice NON entra
console.log("Hai un punteggio!");
}
-
if / else - Il Bivio a Due Vie Se
ifè il bivio,elseè l'altra strada. È il "Piano B" garantito. Se la condizioneifè falsa, il bloccoelseviene eseguito.const eta = 15;
if (eta >= 18) {
console.log("Accesso consentito: Maggiorenne");
} else {
console.log("Accesso negato: Minorenne");
} -
if / else if / else - La Rotatoria a Più Uscite Quando hai più di due scelte, puoi incatenare gli
else ifper creare una serie di controlli.const voto = 85;
if (voto > 90) {
console.log("A");
} else if (voto > 80) {
console.log("B"); // Entra qui!
} else if (voto > 70) {
console.log("C");
} else {
console.log("F");
}JavaScript esegue i controlli in ordine e si ferma al primo che risulta
true.
Operatore Ternario - Il Bivio Compatto (per Assegnazioni)
L'operatore ternario è una versione ultra-compatta di if/else. Pensa a if/else come a una lettera formale, e al ternario come a un post-it.
La sua sintassi è: condizione ? valore_se_true : valore_se_false;
È progettato per restituire un valore, quindi è perfetto per assegnare dati a una variabile.
const eta = 20;
// Modo classico
let status;
if (eta >= 18) {
status = "Maggiorenne";
} else {
status = "Minorenne";
}
// Modo ternario (più pulito)
const statusTernario = eta >= 18 ? "Maggiorenne" : "Minorenne";
// Leggilo come: "L'età è >= 18? Se sì, usa 'Maggiorenne', altrimenti usa 'Minorenne'."
Quando usarlo? Usalo solo per assegnazioni semplici. Se inizi a "annidare" i ternari (un ternario dentro l'altro), stai creando un mostro illeggibile. Per logica complessa, if/else è sempre la scelta migliore per la chiarezza.
switch - Il Centralino Telefonico
Se if/else è una rotatoria, switch è un centralino telefonico. È perfetto quando hai una singola variabile (l'"interno" che vuoi chiamare) da confrontare con una lista di valori statici (gli "interni disponibili"). È molto più pulito di una lunga catena di if (x === 1) else if (x === 2) else if (x === 3)....
const azione = "salva";
switch (azione) { // 1. Il valore da controllare
case "salva": // 2. "Corrisponde a 'salva'?"
console.log("Dati salvati.");
break; // 3. FONDAMENTALE: esci!
case "carica":
console.log("Caricamento...");
break;
case "elimina":
case "cancella": // 4. "Fall-through": raggruppa più casi
console.log("Elemento eliminato.");
break;
default: // 5. L'"else" dello switch
console.log("Azione sconosciuta.");
}
I Segreti di switch:
- Il
breakè il tuo migliore amico: Dimenticarebreakè l'errore più comune. Senza di esso, il codice "cade attraverso" (fall-through) ed esegue anche il caso successivo, e quello dopo ancora, fino a che non trova unbreako la fine. È come un centralinista che ti collega all'ufficio giusto, ma dimentica di scollegare quello precedente, e ora sei in una conference call con tutto l'edificio. - Confronto Stretto (
===):switchusa un confronto di identità stretto (come===). Questo significa cheswitch(5)non corrisponderà acase "5", perché un numero non è una stringa.
Pattern "Return Early" - La Guardia all'Ingresso
Questo non è un comando, ma un pattern, un modo di scrivere codice più pulito e robusto. Pensa a questa tecnica come a una guardia di sicurezza (o "buttafuori") all'ingresso di una funzione.
Il modo "classico" di scrivere una funzione è controllare le condizioni positive, creando una "piramide della rovina" (pyramid of doom) di if annidati.
// CATTIVO: La piramide 👎
function processaPagamento(utente, carrello) {
if (utente) {
if (utente.cartaDiCreditoValida) {
if (carrello.totale > 0) {
// ...finalmente, il codice che ci interessa...
// ...sepolto dentro 3 livelli di indentazione...
eseguiPagamento(carrello.totale, utente.carta);
} else {
console.error("Carrello vuoto");
}
} else {
console.error("Carta non valida");
}
} else {
console.error("Utente non loggato");
}
}
Questo codice è difficile da leggere. Il "percorso felice" (quello che fa il vero lavoro) è nascosto in fondo.
Il pattern Return Early (o Guard Clauses) ribalta la logica: controlla prima tutte le condizioni negative e esci subito (return) se qualcosa non va.
// BUONO: Piatto e leggibile (Guard Clauses) 👍
function processaPagamento(utente, carrello) {
// 1. Guardia: l'utente esiste?
if (!utente) {
console.error("Utente non loggato");
return; // Esci subito
}
// 2. Guardia: la carta è valida?
if (!utente.cartaDiCreditoValida) {
console.error("Carta non valida");
return; // Esci subito
}
// 3. Guardia: il carrello è pieno?
if (carrello.totale <= 0) {
console.error("Carrello vuoto");
return; // Esci subito
}
// Se siamo arrivati qui, è tutto valido.
// Il "percorso felice" è piatto e facile da leggere.
eseguiPagamento(carrello.totale, utente.carta);
}
Questo stile è immensamente superiore perché:
- Riduce l'indentazione.
- Rende la logica principale della funzione immediatamente visibile.
- Gestisce tutti i casi limite all'inizio, come un buttafuori che filtra la coda.
10. Cicli - Le Ripetizioni Automatizzate
I cicli sono l'essenza dell'automazione. Sono il modo in cui dici al computer: "Fai questa cosa, e poi falla ancora, e ancora... finché non ti dico di smettere." Senza cicli, dovresti scrivere console.log(1), console.log(2)... mille volte. Con un ciclo, lo scrivi una volta sola.
Pensa ai cicli come a diversi tipi di robot operai, ognuno specializzato per un compito diverso.
for - Il Ciclo Contatore (Il Robot Industriale)
Il ciclo for è il tuo robot industriale su un nastro trasportatore. È preciso, metodico e sa esattamente cosa deve fare prima ancora di iniziare. È perfetto quando sai in anticipo quante volte devi ripetere un'azione.
La sua sintassi è come il suo pannello di controllo, con tre impostazioni fondamentali:
for (inizializzazione; condizione; incremento) { ... }
- Inizializzazione (
let i = 0): Il "punto di partenza". Il robot imposta il suo contatore a 0. Questa variabilei(sta per "indice") vive solo all'interno del ciclo, grazie al Block Scope dilet. - Condizione (
i < 5): Il "limite di lavoro". Prima di ogni singolo giro, il robot controlla: "Il mio contatore è ancora sotto 5?". Se sì, lavora. Se no, si ferma. - Incremento (
i++): L'azione "dopo il lavoro". Dopo aver completato un giro, il robot preme il pulsante per far avanzare il nastro e incrementa il suo contatore (idiventa 1, poi 2, ecc.).
// Stampa i numeri da 0 a 4
for (let i = 0; i < 5; i++) {
console.log(`Iterazione ${i}`);
}
// Output: 0, 1, 2, 3, 4
Quando usarlo? È la scelta perfetta per iterare su un array quando hai bisogno dell'indice:
const arr = ["a", "b", "c"];
for (let i = 0; i < arr.length; i++) {
console.log(`Indice ${i}: ${arr[i]}`);
}
while - Il Ciclo Condizionale (La Guardia Notturna)
Se for è un robot industriale, while è una guardia notturna. Non sa quanti giri farà stanotte. Sa solo che deve "continuare a pattugliare mentre (while) la porta principale è chiusa".
Controlla la condizione prima di fare qualsiasi cosa.
while (condizione) { ... }
- Controlla la
condizione. - Se è
true, esegue il blocco di codice. - Torna al punto 1 e ricontrolla.
Il Pericolo: Il Loop Infinito!
La guardia notturna deve avere un modo per cambiare la condizione. Se la porta non si apre mai (e la guardia non ha una chiave), pattuglierà per sempre. Devi sempre assicurarti che qualcosa all'interno del while (o all'esterno) prima o poi renda la condizione false.
let tentativi = 0;
let passwordInserita = "";
while (passwordInserita !== "segreta" && tentativi < 3) {
console.log(`Tentativo ${tentativi + 1}`);
// Qualcosa DEVE cambiare la condizione
passwordInserita = prompt("Inserisci password:"); // Modifica la condizione
tentativi++; // Modifica la condizione
}
if (passwordInserita === "segreta") {
console.log("Accesso consentito!");
} else {
console.log("Account bloccato.");
}
Quando usarlo? Quando non sai quante iterazioni serviranno, ma sai a quale condizione devi fermarti. (Es: "continua a scaricare dati finché non c'è più niente", "continua a chiedere all'utente finché non risponde 'sì'").
do...while - Il Ciclo Garantito (Prima Fai, Poi Chiedi)
Questo è il cugino impulsivo di while. È una guardia notturna che fa almeno un giro di pattuglia prima ancora di controllare se la porta è chiusa. È il "prima spara, poi fai domande".
do { ... } while (condizione);
- Esegue il blocco
do(la prima volta, sempre! Senza fare domande). - Poi, alla fine del giro, controlla la
condizione. - Se è
true, torna al punto 1 e ripete.
Quando usarlo? Quando devi eseguire l'azione almeno una volta, indipendentemente dalla condizione. È il re indiscusso per creare menu interattivi.
let scelta;
do {
console.log("--- MENU ---");
console.log("1. Gioca");
console.log("2. Opzioni");
console.log("3. Esci");
scelta = prompt("Scegli un'opzione (1-3):");
// ... logica per scelta 1 e 2 ...
} while (scelta !== "3"); // Continua a mostrare il menu finché non sceglie "3"
console.log("Arrivederci!");
for...of - Il Ciclo per Collezioni (L'Esploratore Elegante)
Questo è il modo moderno ed elegante di ciclare. È come avere una scatola magica (const carrello) e dire a JavaScript: "esamina ogni oggetto (const prodotto) nella (of) scatola, uno alla volta, non mi interessa l'ordine o l'indice, dammeli e basta".
for (const elemento of iterabile) { ... }
- Funziona magicamente su "iterabili": Array, Stringhe, Map, Set, e le
NodeListdel DOM. - Non funziona sugli oggetti semplici
{}(non sono iterabili in questo modo).
Vantaggi:
- Leggibilità:
for (const prodotto of carrello)è molto più pulito difor (let i = 0; i < carrello.length; i++) { ... }. - Sicurezza:
const elementopreviene modifiche accidentali. - Senza Indici: Non devi preoccuparti di
i,length, o di sbagliare coni <= length.
// Esempio 1: Iterare su un Array
const carrello = ["mele", "pane", "latte"];
for (const prodotto of carrello) {
console.log(`Comprare: ${prodotto}`);
}
// Esempio 2: Iterare su una Stringa
for (const lettera of "Ciao") {
console.log(lettera); // Stampa C, i, a, o
}
Distinzione Fondamentale: for...of vs for...in
Non confonderli!
for...ofitera sui valori di un iterabile (Array, Stringa...). È quello che vuoi il 99% delle volte.for...initera sulle chiavi (proprietà) di un oggetto.
const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key); // Stampa "a", "b" (le chiavi!)
}
forEach - L'Iteratore di Array (Il Caposquadra)
forEach non è un ciclo "nativo" di JavaScript (come for o while), è un metodo che vive solo sugli Array. È come un caposquadra che dice: "Per ciascun (forEach) operaio (elemento) nella mia squadra (array), digli di eseguire questo compito (la callback)".
array.forEach(function(elemento, indice) { ... });
- Accetta una callback (un'istruzione) che viene eseguita per ogni elemento.
- La callback riceve automaticamente
(elemento, indice, arrayCompleto)come argomenti.
const frutti = ["mela", "pera", "banana"];
frutti.forEach((frutto, indice) => {
console.log(`${indice + 1}. ${frutto}`);
});
// Output:
// 1. mela
// 2. pera
// 3. banana
Il "Difetto" (o Caratteristica): Non Puoi Fermarlo!
forEach è come un treno che deve visitare tutte le stazioni. I comandi break e continue NON FUNZIONANO al suo interno. Se hai bisogno di fermarti a metà (ad esempio, per cercare un elemento), non usare forEach. Usa un ciclo for classico, for...of, o i metodi .find()/.some().
Controllo del Flusso nei Cicli (I Teletrasporti)
break e continue sono i "teletrasporti" del ciclo. Sono i comandi di emergenza per il tuo robot. Funzionano in for, while e do...while (ma non in forEach).
break - Il Pulsante di Stop Emergenza 🚨
- Cosa fa: Ferma immediatamente l'intero ciclo (quello più interno in cui si trova) e "salta" fuori, continuando l'esecuzione del codice dopo il ciclo.
- Analogia: È il pulsante di emergenza rosso. Non importa quanti bulloni mancano, il robot si ferma e il nastro trasportatore si spegne.
// Trova il primo numero pari e fermati
const numeri = [1, 3, 5, 6, 7, 9, 8];
let primoPari;
for (const num of numeri) {
if (num % 2 === 0) {
primoPari = num;
break; // Trovato! Esci dal ciclo. Non continuare a cercare.
}
}
// primoPari è 6 (non 8)
continue - Salta al Prossimo Giro ⏭️
- Cosa fa: Ferma solo l'iterazione corrente e "salta" immediatamente all'inizio del giro successivo (all'incremento
i++nelfor, o al controllowhile (condizione)). - Analogia: È il comando "scarta questo pezzo". Il robot vede un bullone difettoso, lo butta via (
continue) e passa subito al bullone successivo sul nastro trasportatore, senza completare le altre operazioni per quello difettoso.
// Stampa solo i numeri dispari
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) { // Se è pari...
continue; // ...salta questo giro, non eseguire il console.log.
}
// Questo codice viene eseguito solo se `continue` non è scattato
console.log(i); // 1, 3, 5, 7, 9
}
Funzioni e Scope
11. Funzioni - Le Ricette Riutilizzabili del Codice
Le funzioni sono i mattoni fondamentali di un programma ben organizzato. Sono come delle ricette: definisci una serie di passaggi una sola volta (es. "come fare una torta") e poi puoi "cucinare" quel risultato ogni volta che vuoi, semplicemente chiamando la ricetta e fornendo gli ingredienti (i "parametri").
Sono il tuo strumento principale per applicare il principio DRY (Don't Repeat Yourself). Se ti trovi a scrivere lo stesso blocco di codice più di una volta, è il momento di trasformarlo in una funzione.
Dichiarazione Classica vs Arrow Functions
Esistono due modi principali per scrivere una funzione, ognuno con le sue caratteristiche.
-
Dichiarazione Classica (
function) Questo è il modo tradizionale, robusto e universale. Pensa a questa come alla ricetta formale scritta su una pergamena.// Dichiarazione classica
function saluta(nome) {
return `Ciao, ${nome}!`;
}
// Espressione di funzione (quasi identica, ma assegnata a una variabile)
const salutaComeEspressione = function(nome) {
return `Ciao, ${nome}!`;
};Caratteristiche chiave:
- Vengono "sollevate" (hoisted): Puoi chiamare una funzione classica prima di averla definita nel codice.
- Hanno il loro proprio valore
this, che cambia a seconda di come e dove vengono chiamate (questo è un argomento avanzato, spesso fonte di confusione).
-
Arrow Functions (
=>) Introdotte in ES6, sono il modo moderno, conciso e spesso preferito. Pensa a loro come a una nota rapida su un post-it.const saluta = (nome) => {
return `Ciao, ${nome}!`;
};Caratteristiche chiave:
- Sintassi concisa: Meno "rumore" (niente parola
function). - Niente
thisproprio: Non hanno un lorothis! Lo "ereditano" dal contesto in cui sono state create. Questo risolve enormi mal di testa, specialmente conaddEventListenere metodi di classe.
- Sintassi concisa: Meno "rumore" (niente parola
Return Implicito vs Esplicito
Questo è uno dei superpoteri delle Arrow Functions.
-
Return Esplicito (Con
{}) Se la tua funzione freccia usa le parentesi graffe{}, stai definendo un "blocco di codice" (che può contenere più righe). Come in una funzione classica, devi usare la parola chiavereturnper restituire un valore.// Esplicito (con graffe, serve return)
const somma = (a, b) => {
const risultato = a + b;
return risultato; // Devi scrivere 'return'
}; -
Return Implicito (Senza
{}) Se la tua funzione fa solo una cosa (una singola espressione, un "one-liner"), puoi omettere sia le graffe{}sia la parolareturn. JavaScript restituirà automaticamente il risultato di quell'unica espressione.// Implicito (senza graffe, return automatico)
const somma = (a, b) => a + b;
// Perfetto per i metodi array
const raddoppiati = [1, 2, 3].map(n => n * 2); // [2, 4, 6]
Return Oggetto (con ())
Il Trabocchetto Fondamentale! Cosa succede se vuoi restituire implicitamente un oggetto?
// SBAGLIATO! ❌
const creaUtente = (nome) => { nome: nome, eta: 30 };
// Questo ritorna 'undefined'!
Perché? JavaScript vede la { e pensa che sia l'inizio di un blocco di codice (return esplicito), non un oggetto letterale.
La Soluzione: Avvolgi l'oggetto tra parentesi tonde ().
Questo dice a JavaScript: "Ehi, tratta quello che c'è dentro le graffe come una singola espressione (un oggetto), non come istruzioni."
// CORRETTO! ✅
const creaUtente = (nome) => ({ nome: nome, eta: 30 });
// Questo ritorna correttamente l'oggetto { nome: "Mario", eta: 30 }
È come mettere un'etichetta sulla scatola che dice: "Questo è un oggetto, non una lista di comandi."
Parametri di Default
Un modo pulito (ES6+) per rendere le tue funzioni più robuste, fornendo "valori di fallback" per i parametri che non vengono passati.
Analogia: Una ricetta che dice "un pizzico di sale (o 1g se non sai cos'è 'un pizzico')".
// Prima dovevi fare così (goffo)
function salutaVecchio(nome) {
nome = nome || "Ospite";
return `Ciao, ${nome}`;
}
// Ora (molto più pulito)
function saluta(nome = "Ospite", orario = "giorno") {
return `Buon${orario}, ${nome}!`;
}
saluta(); // "Buongiorno, Ospite!"
saluta("Mario"); // "Buongiorno, Mario!"
saluta("Mario", "asera"); // "Buonasera, Mario!"
Destrutturazione nei Parametri
Questo è un pattern avanzato ma incredibilmente pulito. Ti permette di "spacchettare" (destrutturare) un oggetto o un array direttamente nella firma della funzione.
Analogia: Invece di ricevere un intero cesto di frutta (l'oggetto utente) e dover poi estrarre la mela (utente.nome) e la banana (utente.eta), dici alla funzione: "Mi servono solo la mela e la banana, dammi quelle direttamente."
const utente = { id: 1, nome: "Mario", eta: 30 };
// Senza destrutturazione (goffo)
function presentaPersona(u) {
return `${u.nome} ha ${u.eta} anni`;
}
// Con destrutturazione (pulito!)
function presentaPersona({ nome, eta }) {
return `${nome} ha ${eta} anni`;
}
presentaPersona(utente); // "Mario ha 30 anni"
// Il top: destrutturazione + parametri di default
function configura({ tema = "light", volume = 50 } = {}) {
// ...
}
configura(); // Funziona senza errori, usando i default
Callback (Concetto Base)
Questo è un concetto fondamentale in JavaScript. Una callback è una funzione che viene passata come argomento a un'altra funzione, con l'intenzione di essere "richiamata" (called back) in un secondo momento.
Analogia: L'Ordine della Pizza 🍕
- Tu chiami la pizzeria (la funzione
ordinaPizza). - Non resti al telefono ad aspettare che la pizza sia pronta (il codice non si blocca).
- Dai al pizzaiolo il tuo numero di telefono (la callback).
- Quando la pizza è pronta (l'evento), il fattorino ti chiama (esegue la callback).
Perché è Fondamentale?
-
Asincronia (La Pizza): Per operazioni che richiedono tempo (download, timer). Permette al tuo codice di "continuare a fare altro" mentre aspetta.
console.log("Ordino...");
// setTimeout è una funzione che accetta una callback e un tempo
setTimeout(() => { // Questa è la callback (il tuo numero)
console.log("🍕 Pizza arrivata!"); // Eseguita dopo 2 secondi
}, 2000);
console.log("Intanto apparecchio la tavola."); -
Specializzazione (La Macchina): Per dire a una funzione generica cosa fare.
mapè una macchina generica che "scorre un array". La tua callback è l'istruzione specifica (es. "raddoppia il numero") da eseguire a ogni passo.const numeri = [1, 2, 3];
const raddoppia = n => n * 2; // Questa è la callback (l'istruzione)
const doppi = numeri.map(raddoppia); // [2, 4, 6]
Currying
Il Currying è una tecnica di programmazione funzionale che trasforma una singola funzione con multipli argomenti (es. fn(a, b, c)) in una sequenza di funzioni, ognuna con un solo argomento (es. fn(a)(b)(c)).
Analogia: Il Cuoco Specializzato 👨🍳
preparaPiatto(ing1, ing2, ing3): È un cuoco che ha bisogno di tutti e 3 gli ingredienti subito per iniziare a cucinare.preparaPiattoCurry(ing1): Tu dai al cuoco il primo ingrediente (es. "pasta"). Lui non ti restituisce il piatto finito. Invece, ti restituisce un nuovo cuoco specializzato che ora sa solo come fare piatti a base di pasta.chefPasta(ing2): Tu dai a questo nuovo cuoco "pomodoro". Lui ti restituisce un cuoco ancora più specializzato che sa fare solo pasta al pomodoro e aspetta l'ultimo ingrediente.
Vantaggio (Partial Application)
Il vero potere non è chiamare fn(a)(b)(c) tutto in una volta. È salvare i cuochi specializzati! Questo si chiama "Applicazione Parziale" (Partial Application).
// Funzione "curryficata"
const curriedAdd = (a) => { // Il primo chef
return (b) => { // Il cuoco specializzato
return a + b;
};
};
// --- Applicazione Parziale ---
// Creiamo un cuoco specializzato!
const add10 = curriedAdd(10);
// add10 è ora una *nuova funzione* (b => 10 + b)
// È un "cuoco" che ha il 10 "bloccato" dentro di sé.
// Ora usiamo il nostro cuoco specializzato quando vogliamo
console.log(add10(5)); // 15
console.log(add10(20)); // 30
console.log(add10(90)); // 100
È incredibilmente utile per creare funzioni riutilizzabili e configurabili.
Come Funziona (Closure): Questo è possibile solo grazie alla Closure. La funzione interna (b => ...) "si ricorda" del valore di a anche dopo che la funzione esterna ha finito di essere eseguita.
Sintassi (Arrow Function)
Il Currying e le Arrow Function (con return implicito) sono fatti l'uno per l'altra.
// La versione lunga (con return espliciti)
const curriedAddLunga = (a) => {
return (b) => {
return a + b;
};
};
// La versione "compressa" con arrow functions
const curriedAdd = a => b => a + b;
// Funzionano allo stesso modo!
const add5 = curriedAdd(5);
console.log(add5(3)); // 8
Convenzione Underscore (_) per Parametri Non Usati
Questa non è una regola di JavaScript, ma una convenzione stilistica (un "patto tra gentiluomini" tra programmatori).
A volte, una funzione (specialmente una callback) ti fornisce più parametri, ma a te ne serve solo uno successivo.
Esempio: arr.map((elemento, indice) => ...)
E se volessi solo l'indice e non l'elemento? Non puoi scrivere arr.map((indice) => ...) perché JavaScript penserà che indice sia il primo parametro (l'elemento).
Analogia: La Posta 📬
Devi controllare la posta per trovare l'unica bolletta importante. La cassetta contiene (pubblicità, bolletta, rivista).
Sei costretto a prendere anche la pubblicità per arrivare alla bolletta.
La Soluzione: Definisci comunque il parametro, ma chiamalo _ (o _elemento) per segnalare a chi legge (e agli strumenti di analisi del codice) che: "Sì, so che questo parametro esiste, ma l'ho intenzionalmente ignorato."
// Voglio creare un array di indici [0, 1, 2]
const arr = ["a", "b", "c"];
// Non mi serve il valore ("a", "b", "c"), solo l'indice
const indici = arr.map((_elemento, indice) => {
return indice;
});
// indici è [0, 1, 2]
// L'_elemento dice "ignoro il primo parametro, non è un bug"
12. Scope - La Visibilità delle Variabili
Lo Scope (o campo di visibilità) è l'insieme di regole che determina dove una variabile è accessibile nel tuo codice. Non è un concetto astratto, è una regola fisica del linguaggio, come la gravità.
Pensa al tuo programma come a una grande casa (Global Scope). Dentro questa casa, ci sono molte stanze private (Function Scope) e dentro quelle stanze ci sono dei ripostigli chiusi (Block Scope).
Lo Scope risponde alla domanda: "Se mi trovo in questa stanza, quali variabili posso vedere e usare?"
Global Scope vs Local Scope
Questa è la distinzione fondamentale, la differenza tra la piazza della città e il tuo salotto di casa.
-
Global Scope (La Piazza) Una variabile è nello Scope Globale se è dichiarata al di fuori di qualsiasi funzione o blocco.
// QUESTE SONO GLOBALI
let punteggioMassimo = 1000;
const NOME_GIOCO = "Avventura JS";
function mostraPunteggio() {
console.log(punteggioMassimo); // Posso vederla da qui!
}Analogia: È un monumento nella piazza principale. Chiunque, da qualsiasi finestra di qualsiasi palazzo (funzione), può affacciarsi e vederlo. Il Pericolo: È anche un "inquinamento". Se troppe cose sono globali, chiunque può anche provare a modificarle (se sono
let). È come lasciare il portafoglio su una panchina pubblica: comodo, ma pericoloso. -
Local Scope o Function Scope (Il Salotto) Una variabile è nello Scope Locale se è dichiarata all'interno di una funzione.
function gioca() {
// QUESTA È LOCALE
let punteggioRound = 100;
console.log(`Hai totalizzato ${punteggioRound} punti!`);
// Posso vedere anche il globale da qui
console.log(`Il punteggio massimo è ${punteggioMassimo}`);
}
gioca();
// console.log(punteggioRound); // ERRORE! ❌
// ReferenceError: punteggioRound is not definedAnalogia:
punteggioRoundè il telecomando del tuo salotto. Esiste solo in quella stanza. Chi è fuori (Global Scope) non può vederlo, non può usarlo e non sa nemmeno che esiste. Quando esci dalla stanza (la funzione finisce), il telecomando "sparisce" (la variabile viene distrutta dal Garbage Collector).
Scope Chain - La Catena di Visibilità
Ok, ma come fa JavaScript a decidere quale variabile usare? Segue un processo chiamato Scope Chain (Catena di Visibilità).
Pensa a quando cerchi le chiavi di casa:
- Guardi nelle tue tasche (lo Scope Attuale, quello più interno). Le trovi? Perfetto, ti fermi e le usi.
- Non sono nelle tasche? Guardi sul tavolo del salotto (lo Scope Esterno, la funzione che ti contiene). Le trovi? Ok, usi quelle.
- Non sono sul tavolo? Guardi sull'attaccapanni all'ingresso (lo Scope Globale). Trovate? Usi quelle.
- Non sono nemmeno lì? Ti arrendi. (JavaScript lancia un
ReferenceError).
JavaScript fa esattamente la stessa cosa. Cerca la variabile nel suo scope attuale e, se non la trova, "risale" la catena degli scope, un anello alla volta, fino a raggiungere quello Globale.
Il Concetto di "Shadowing" (Ombreggiatura) Cosa succede se hai due variabili con lo stesso nome a livelli diversi? Vince quella più vicina!
const messaggio = "Globale"; // 3. Attaccapanni all'ingresso
function esterna() {
const messaggio = "Esterna"; // 2. Tavolo del salotto
function interna() {
const messaggio = "Interna"; // 1. Nelle tue tasche
console.log(messaggio); // Cerca... e la trova subito!
}
interna(); // Output: "Interna"
console.log(messaggio); // Cerca... la trova sul "tavolo"
}
esterna(); // Output: "Esterna"
console.log(messaggio); // Cerca... la trova all'"ingresso"
// Output: "Globale"
La variabile messaggio = "Interna" "mette in ombra" (shadows) quella esterna, e quella esterna mette in ombra quella globale.
Block Scope con let e const
Questa è la rivoluzione moderna (introdotta con ES6).
- Prima, solo le
functioncreavano una "stanza" (Function Scope). - Ora, con
leteconst, qualsiasi coppia di parentesi graffe{}crea un "ripostiglio" (un Block Scope).
Questo include if, else, for, while, o anche solo delle graffe messe a caso.
var (il vecchio modo) IGNORA i blocchi:
if (true) {
var x = 10;
}
console.log(x); // 10
// 'x' è "scappata" dal blocco if! È come se il ripostiglio non avesse la porta.
let e const RISPETTANO i blocchi:
if (true) {
let y = 20;
const z = 30;
}
// console.log(y); // ERRORE! y is not defined
// console.log(z); // ERRORE! z is not defined
// 'y' e 'z' sono rinchiuse nel ripostiglio `{}`.
Perché questo è FONDAMENTALE: I Cicli for
Questo è l'esempio che fa capire tutto. Guarda il "bug" classico con var:
// Il "bug" classico di var
for (var i = 0; i < 3; i++) {
setTimeout(() => {
// Quando questo codice viene eseguito, il loop è GIÀ FINITO.
// La variabile 'i' è stata sollevata (hoisted)
// e il suo valore finale è 3.
console.log(i);
}, 100);
}
// Output: 3, 3, 3
Questo succede perché c'è una sola i per tutto il ciclo, che vive nello scope della funzione.
Ora guarda la magia di let:
// Con 'let', ogni giro del loop crea una NUOVA 'i'
for (let i = 0; i < 3; i++) {
// Ogni 'i' (0, 1, 2) è una copia diversa,
// "congelata" nello scope del suo specifico giro di loop (grazie alla closure)
setTimeout(() => {
console.log(i);
}, 100);
}
// Output: 0, 1, 2 (Come ti aspetteresti!)
Usare let nei cicli for ti salva da mal di testa inimmaginabili. È come avere un ripostiglio separato per ogni singolo giro del nastro trasportatore.