JavaScript
Fundamentals

Types · Operations · Scope · Control Flow · Functions · this · ES6 Classes

Tipi di base

JavaScript ha 7 tipi primitivi (immutabili, passati per valore) e poi gli oggetti (passati per riferimento). Conoscere la distinzione evita bug sottili.

number
42 3.14 NaN Infinity
string
"hello" 'world' `tmpl`
boolean
true false
null
assenza intenzionale
undefined
non ancora assegnato
symbol
identificatore unico
bigint
9007199254740993n
object
{} [] function null*
Motivazione I primitivi sono immutabili: quando scrivi let a = "ciao"; let b = a; b riceve una copia indipendente. Gli oggetti invece condividono il riferimento: modificare uno modifica anche l'altro. Capire questo previene la maggior parte dei bug "perché è cambiato anche quello?".
typeof 42          // "number"
typeof "ciao"       // "string"
typeof true         // "boolean"
typeof undefined    // "undefined"
typeof null         // "object"  ← storico bug di JS
typeof {}           // "object"
typeof []           // "object"  ← array è un oggetto!
typeof function(){} // "function"

Coercizione implicita

JS converte automaticamente i tipi in molte operazioni. Questo è sia una comodità che una fonte di sorprese.

"5" + 3    // "53"   → + con stringa = concatenazione
"5" - 3    // 2      → - forza conversione numerica
true + 1   // 2      → true vale 1
false + 1  // 1
null + 1   // 1      → null vale 0 in contesto numerico
"" == false // true  ← ==  fa coercizione, evitarlo!
"" === false// false ← === non fa coercizione, usare sempre
Regola pratica Usa sempre === invece di ==. Il doppio uguale applica coercizione implicita, il triplo no. L'unica eccezione accettata è x == null che cattura sia null che undefined.

Operazioni di base

OperatoreSignificato
+ - * / %aritmetica (% = resto)
**potenza: 2**10 = 1024
=== !==uguaglianza/disuguaglianza stretta
< > <= >=confronto
&& || !AND, OR, NOT logici
??nullish coalescing: a ?? b → b se a è null/undefined
?.optional chaining: obj?.prop (non lancia se obj è null)
++ --incremento/decremento
+= -= *=assegnamento composto
let a = 10, b = 3;
a % b           // 1   (10 mod 3)
a ** b           // 1000

let user = null;
user?.name        // undefined — non lancia TypeError

let val = user ?? "ospite";  // "ospite"

// short-circuit: && si ferma al primo falsy, || al primo truthy
true && console.log("eseguito");
false && console.log("mai");
Motivazione ?? e ?. (ES2020) risolvono un problema classico: prima si scriveva user && user.name. Il nullish coalescing è più preciso di || perché || si attiva anche su 0, "", false (falsy ma validi), mentre ?? solo su null/undefined.

Scope e var / let / const

Lo scope è la regione di codice in cui una variabile è visibile. JavaScript ha tre parole chiave di dichiarazione, con comportamenti profondamente diversi.

scopehoistingre-assegnabilere-dichiarabile
varfunzionesì (→ undefined)
letblocco {}TDZ*no
constblocco {}TDZ*no (binding)no

* TDZ = Temporal Dead Zone: la variabile esiste ma accedervi prima della dichiarazione lancia ReferenceError.

Il problema di var

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// stampa: 3 3 3  ← var sfugge dal blocco for!

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}
// stampa: 0 1 2  ← let crea una nuova binding per ogni iterazione

Hoisting di var

console.log(x); // undefined — NON ReferenceError!
var x = 5;
// var viene "issata" in cima alla funzione come: var x; (vale undefined)

console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;    // TDZ: l'errore è esplicito e utile

const non significa immutabile

const obj = { x: 1 };
obj.x = 99;      // OK — il contenuto è mutabile
obj = {};        // TypeError — il binding è costante

const arr = [1,2];
arr.push(3);      // OK
Motivazione var è rimasto per retrocompatibilità. let e const (ES6, 2015) correggono i suoi difetti. La regola pratica moderna: usa sempre const di default; passa a let solo se la variabile va riassegnata. Non usare mai var in codice nuovo.

if / else if / else

Il ramo condizionale fondamentale. La condizione viene valutata come truthy/falsy, non necessariamente come booleano esatto.

if (condizione) {
  // eseguito se truthy
} else if (altraCondizione) {
  // eseguito se la prima è falsy e questa è truthy
} else {
  // eseguito se tutte le precedenti sono falsy
}

Valori falsy in JS

// Questi sono falsy — tutto il resto è truthy:
false   0   -0   0n   ""   null   undefined   NaN

// Truthy (anche questi sorprendono i principianti):
"0"    // stringa non-vuota è truthy!
[]     // array vuoto è truthy!
{}     // oggetto vuoto è truthy!

Operatore ternario

const msg = age >= 18 ? "adulto" : "minore";

// equivale a:
let msg;
if (age >= 18) msg = "adulto";
else           msg = "minore";
Motivazione Il ternario è un'espressione (restituisce un valore), l'if è un'istruzione. Questo lo rende usabile dentro const, template literals, JSX. Non annidarlo troppo: più di un livello di ternario diventa illeggibile.

switch

Confronto con uguaglianza stretta (===) su un singolo valore contro più casi. Utile quando hai molti rami su uno stesso valore discreto.

switch (giorno) {
  case "lunedì":
  case "martedì":
    console.log("inizio settimana");
    break;             // ← FONDAMENTALE: senza break, cade nel case dopo
  case "venerdì":
    console.log("TGIF");
    break;
  default:
    console.log("metà settimana");
}

Fall-through intenzionale

switch (livello) {
  case "admin":
    grantAdmin();      // fall-through voluto:
  case "user":
    grantAccess();     // admin esegue ENTRAMBI
    break;
}
Attenzione Il fall-through (dimenticare break) è la trappola più comune di switch. Molti team usano un commento esplicito // fallthrough per indicare che è intenzionale, così il code review non confonde con un bug.
Quando usarlo Switch è leggibile per 4+ casi su un valore discreto (enum, stringhe di stato, codici). Per 1-3 casi preferire if/else. Per logica complessa per caso, valutare una lookup table: const actions = { "a": fn1, "b": fn2 }; actions[key]?.();

while / do…while

while esegue il corpo finché la condizione è truthy. Adatto quando non conosci a priori il numero di iterazioni.

let n = 1;
while (n < 100) {
  n *= 2;
}
// n = 128 (prima potenza di 2 ≥ 100)

do…while

Esegue il corpo almeno una volta, poi controlla la condizione.

let input;
do {
  input = prompt("Inserisci un numero positivo:");
} while (input <= 0);   // ripete se non valido

break e continue

let i = 0;
while (true) {
  i++;
  if (i % 2 === 0) continue;  // salta i pari
  if (i > 9)       break;     // esce dal loop
  console.log(i);              // 1 3 5 7 9
}
Motivazione while è il loop più primitivo e generale. for è zucchero sintattico su while. do-while è raro ma elegante quando la prima esecuzione è sempre necessaria (validazione input, algoritmi che producono almeno un risultato).

for (varianti)

for classico

for (let i = 0; i < 5; i++) {
  console.log(i);   // 0 1 2 3 4
}
// init; condizione; aggiornamento

for…of (iterables: array, string, Map, Set)

const colori = ["rosso", "verde", "blu"];
for (const c of colori) {
  console.log(c);
}

// con indice:
for (const [i, c] of colori.entries()) {
  console.log(i, c);
}

for…in (chiavi enumerabili di un oggetto)

const punto = { x: 1, y: 2, z: 3 };
for (const k in punto) {
  console.log(k, punto[k]);   // x 1 / y 2 / z 3
}
Attenzione Non usare for…in sugli array: itera anche proprietà ereditate dal prototipo, e l'ordine non è garantito. Per gli array, usa sempre for…of o i metodi .forEach / .map / .filter.

Metodi array (preferiti in stile funzionale)

const nums = [1,2,3,4,5];

nums.forEach(n => console.log(n));        // effetti collaterali
const doppi   = nums.map(n => n * 2);     // [2,4,6,8,10]
const pari    = nums.filter(n => n % 2 === 0); // [2,4]
const somma   = nums.reduce((acc,n) => acc+n, 0); // 15

Funzioni

In JS le funzioni sono first-class citizens: possono essere assegnate a variabili, passate come argomenti, restituite da altre funzioni.

Dichiarazione (hoisted)

saluta("Dario");   // OK — hoisted, usabile prima della dichiarazione

function saluta(nome) {
  return `Ciao, ${nome}!`;
}

Espressione (non hoisted)

const somma = function(a, b) {
  return a + b;
};

Arrow function (ES6)

const quadrato = x => x * x;              // corpo singolo → return implicito
const add       = (a, b) => a + b;
const multiline = (a, b) => {
  const r = a + b;
  return r;
};

Parametri avanzati (ES6)

// default value
function power(base, exp = 2) { return base ** exp; }

// rest params (raccoglie argomenti extra in array)
function sum(...nums) {
  return nums.reduce((a,b) => a+b, 0);
}
sum(1, 2, 3, 4);  // 10

// destructuring nei parametri
function greet({ name, lang = "it" }) {
  return lang === "it" ? `Ciao ${name}` : `Hello ${name}`;
}

Closure

function counter() {
  let n = 0;
  return () => ++n;   // la funzione interna "chiude" su n
}
const c1 = counter();
c1();  // 1
c1();  // 2   — n persiste tra le chiamate, ma è privato
Motivazione La closure è il meccanismo che permette a JS di emulare variabili private, creare factory, implementare memoization. È il cuore del pattern module. Ogni volta che una funzione "ricorda" il suo contesto lessicale anche dopo che il contesto esterno è terminato, stai vedendo una closure.

this nelle funzioni

this è forse il concetto più confuso di JS. Non dipende da dove la funzione è definita, ma da come viene chiamata (tranne le arrow function).

Contesto di chiamatathis vale
funzione libera (strict)undefined
funzione libera (non-strict)window / global
metodo di oggetto: obj.f()obj
new F()nuovo oggetto creato
f.call(x, a, b)x (esplicito)
f.apply(x, [a,b])x (esplicito)
f.bind(x)(…)x (legato permanentemente)
arrow functionthis del contesto lessicale
const persona = {
  nome: "Dario",
  saluta() {
    console.log(this.nome);  // "Dario" — this = persona
  }
};
persona.saluta();

const fn = persona.saluta;
fn();                // undefined (strict) — this è perso!

Arrow function: this lessicale

function Timer() {
  this.secondi = 0;
  setInterval(function() {
    this.secondi++;        // BUG: this è undefined/global!
  }, 1000);
}

function TimerOK() {
  this.secondi = 0;
  setInterval(() => {
    this.secondi++;        // OK: arrow eredita this da TimerOK
  }, 1000);
}

bind / call / apply

function introduce(saluto) {
  return `${saluto}, sono ${this.nome}`;
}

const p = { nome: "Dario" };
introduce.call(p, "Ciao");          // "Ciao, sono Dario"
introduce.apply(p, ["Salve"]);       // "Salve, sono Dario"
const bound = introduce.bind(p);
bound("Ehi");                        // "Ehi, sono Dario"
Motivazione this dinamico è una scelta progettuale di JS per supportare pattern OOP senza classi (prototype). Le arrow function (ES6) sono state introdotte proprio per risolvere il caso d'uso più comune di perdita di this nei callback. Regola: nelle classi/oggetti usa function per i metodi (così this è l'istanza), usa arrow per i callback interni.

ES6 class

La sintassi class è zucchero sintattico sul sistema a prototipi di JS. Non introduce un nuovo modello OOP: dietro le quinte usa ancora prototype. Ma rende il codice molto più leggibile e gestibile.

class Animale {
  // constructor: chiamato da new Animale(...)
  constructor(nome, verso) {
    this.nome  = nome;
    this.verso = verso;
  }

  // metodo (sulla prototype chain, non duplicato per ogni istanza)
  parla() {
    return `${this.nome} dice ${this.verso}`;
  }

  // getter/setter
  get info() { return `Animale: ${this.nome}`; }

  // metodo statico (su classe, non sull'istanza)
  static crea(nome) { return new Animale(nome, "..."); }
}

class Cane extends Animale {
  constructor(nome, razza) {
    super(nome, "Woof");    // super() chiama il costruttore padre
    this.razza = razza;
  }

  parla() {
    return super.parla() + " (da cane)";  // super.metodo()
  }
}

const fido = new Cane("Fido", "Labrador");
fido.parla();   // "Fido dice Woof (da cane)"
fido.info;      // "Animale: Fido"
fido instanceof Cane;    // true
fido instanceof Animale; // true

Campi privati (ES2022)

class Conto {
  #saldo = 0;                // campo privato (# prefisso)

  deposita(n) { this.#saldo += n; }
  get saldo()  { return this.#saldo; }
}

const c = new Conto();
c.deposita(100);
c.saldo;       // 100
c.#saldo;      // SyntaxError — davvero privato
Motivazione Prima di ES6 (2015) si simulavano le classi con funzioni costruttore e .prototype. La sintassi class non cambia la semantica (è ancora prototipale), ma riduce il boilerplate e rende espliciti constructor, ereditarietà, override — concetti che prima erano impliciti e fragili. I campi privati con # (ES2022) finalmente risolvono l'incapsulamento reale, sostituendo la convenzione informale del prefisso _.