JavaScript — Closures e Scope

Scope lessicale · Closures · La keyword this · 2026 · Marco Consiglio
Volta Institute · Napoli
A.A. 2026
🔒 Cos'è lo scope — dove una variabile "esiste"
Lo scope (visibilità) determina da quali punti del codice una variabile è accessibile. In JavaScript moderno (con let/const), lo scope è a blocco: una variabile esiste solo dentro le graffe { } in cui è stata dichiarata.
function esempio() { const messaggio = 'Ciao dal function scope'; console.log(messaggio); // → funziona, siamo dentro la funzione } esempio(); console.log(messaggio); // → ReferenceError! messaggio non esiste fuori dalla funzione
💡 Questo concetto (già visto nella guida Basi) si chiama scope lessicale: una variabile dichiarata dentro una funzione o un blocco è invisibile all'esterno — è come se ogni { } fosse una stanza con la porta chiusa.
🪆 Scope annidato — le funzioni interne VEDONO le variabili esterne
function esterna() { const valoreEsterno = 'sono esterno'; function interna() { console.log(valoreEsterno); // ✅ funziona! la funzione interna VEDE lo scope esterno } interna(); } esterna();
✅ La regola è direzionale: una funzione interna può sempre "vedere" le variabili delle funzioni che la contengono, ma NON vale il contrario — il codice esterno non può vedere le variabili dichiarate dentro una funzione interna.
🎁 Closures — una funzione che "ricorda" il suo ambiente
Una closure si crea quando una funzione interna viene restituita (o passata altrove) dalla funzione esterna che la contiene, e continua ad avere accesso alle variabili di quell'ambiente esterno — anche dopo che la funzione esterna ha terminato la sua esecuzione.
function creaContatore() { let conteggio = 0; // questa variabile "vive" nella closure return function() { conteggio++; console.log(conteggio); }; } const contatore1 = creaContatore(); // creaContatore() ha già finito di eseguire... contatore1(); // → 1 contatore1(); // → 2 (...ma 'conteggio' è ancora vivo e ricordato!) contatore1(); // → 3
⚠️ Il punto sorprendente: creaContatore() ha già "finito" di eseguire quando chiami contatore1() la prima volta — normalmente le variabili locali di una funzione scompaiono alla fine della sua esecuzione. Con le closure, invece, conteggio resta "vivo" perché la funzione interna restituita continua a fare riferimento ad esso.
🏭 Contatori indipendenti — ogni chiamata crea il proprio ambiente
const contatoreA = creaContatore(); const contatoreB = creaContatore(); contatoreA(); // → 1 contatoreA(); // → 2 contatoreB(); // → 1 (contatoreB ha il SUO proprio 'conteggio' indipendente!)
💡 Ogni chiamata a creaContatore() crea un nuovo ambiente con la propria variabile conteggio indipendente — contatoreA e contatoreB non condividono lo stesso contatore, anche se generati dalla stessa funzione.
🔐 Caso d'uso reale: variabili "private" con closure
function creaAccount(saldoIniziale) { let saldo = saldoIniziale; // non accessibile direttamente dall'esterno return { deposita: (importo) => { saldo += importo; }, preleva: (importo) => { saldo -= importo; }, controllaSaldo: () => saldo }; } const account = creaAccount(100); account.deposita(50); console.log(account.controllaSaldo()); // → 150 console.log(account.saldo); // → undefined! 'saldo' non è accessibile direttamente dall'esterno
✅ Questo pattern (chiamato "incapsulamento") usa la closure per creare variabili che si possono modificare solo tramite funzioni specifiche esposte — un modo per proteggere i dati da modifiche dirette e incontrollate dall'esterno.
👉 La keyword this — dipende da COME viene chiamata la funzione
this si riferisce all'oggetto "proprietario" della funzione in esecuzione — ma il suo valore dipende da COME la funzione viene chiamata, non da dove è definita. È uno dei concetti più confusi di JavaScript.
const studente = { nome: 'Marco', saluta: function() { console.log(`Ciao, sono ${this.nome}`); } }; studente.saluta(); // → Ciao, sono Marco (this = studente, perché chiamata come studente.saluta())
💡 Qui this punta all'oggetto studente perché la funzione è stata chiamata con la notazione oggetto.metodo() — this si "lega" all'oggetto a sinistra del punto al momento della chiamata.
⚠️ Il trabocchetto classico: this in una funzione tradizionale annidata
const studente = { nome: 'Marco', corsi: ['HTML', 'CSS'], mostraCorsi: function() { // Con una FUNCTION tradizionale dentro forEach, this NON è più studente: this.corsi.forEach(function(corso) { console.log(this.nome + ': ' + corso); // ❌ this.nome è undefined qui! }); } };
⚠️ Dentro una function tradizionale passata come callback, this perde il collegamento con l'oggetto esterno. Le arrow function risolvono questo problema, perché non hanno un proprio "this": ereditano quello del contesto in cui sono scritte (vedi Playground per la soluzione).
⚠️ Il bug classico: closure dentro un ciclo con var
// Con "var" (scope di FUNZIONE, non di blocco): for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // → stampa 3, 3, 3 (sorprendente! non 0, 1, 2 come ci si aspetterebbe) // Con "let" (scope di BLOCCO, una nuova variabile per ogni iterazione): for (let j = 0; j < 3; j++) { setTimeout(() => console.log(j), 100); } // → stampa 0, 1, 2 (il comportamento atteso)
⚠️ Con var, esiste UNA SOLA variabile i condivisa da tutte le iterazioni del ciclo: quando i setTimeout eseguono (dopo che il ciclo è già finito), i vale già 3 per tutti. Con let, ogni iterazione crea una nuova variabile con scope di blocco, "catturata" separatamente da ogni closure — questo è uno dei motivi principali per cui oggi si preferisce sempre let/const a var.
🧪 Playground — prova closures, scope e this dal vivo
Editor JS
// il risultato apparirà qui