Arrow functions, closures, callback, IIFE e programmazione funzionale.
Funzioni JavaScript Moderne
In questa lezione esploriamo le funzionalità avanzate delle funzioni JavaScript: sintassi moderna, pattern di programmazione funzionale e concetti potenti come closures e callback.
🎯 In Questa Lezione
- ✅ Arrow Functions - Sintassi ES6 compatta
- ✅ Closures - Funzioni che "ricordano" lo scope
- ✅ Callback - Funzioni passate come argomenti
- ✅ Higher-Order Functions - Funzioni che usano funzioni
- ✅ IIFE - Funzioni auto-eseguibili
Arrow Functions (ES6)
Le arrow functions sono una sintassi più breve per scrivere funzioni, introdotte in ES6 (2015). Molto usate nella programmazione moderna!
- Sintassi Base
Diversi modi di scrivere arrow functions.
Arrow Function Syntax
// Funzione classica
function somma(a, b) {
return a + b;
}
// ✨ Arrow function - forma completa
const sommaArrow = (a, b) => {
return a + b;
};
// ✨ Arrow function - forma breve (return implicito)
const sommaBreve = (a, b) => a + b;
// Test
console.log(somma(5, 3)); // 8
console.log(sommaArrow(5, 3)); // 8
console.log(sommaBreve(5, 3)); // 8
// Un solo parametro - parentesi opzionali
const quadrato = n => n * n;
console.log(quadrato(5)); // 25
// Nessun parametro - parentesi obbligatorie
const saluta = () => "Ciao!";
console.log(saluta()); // "Ciao!"
- Return Implicito
Se la funzione ha una sola espressione, il return è automatico!
Return Implicito
// ✅ Con return implicito (consigliato se breve)
const doppio = n => n * 2;
const isPari = n => n % 2 === 0;
const max = (a, b) => a > b ? a : b;
// Return oggetto - servono parentesi tonde!
const creaUtente = (nome, età) => ({
nome: nome,
età: età,
adulto: età >= 18
});
console.log(doppio(5)); // 10
console.log(isPari(4)); // true
console.log(max(10, 5)); // 10
console.log(creaUtente("Mario", 25));
// {nome: "Mario", età: 25, adulto: true}
// ❌ Senza parentesi tonde per oggetto = ERRORE
// const errore = (nome) => {nome: nome}; // Pensa sia un blocco!
- Differenze con Funzioni Classiche
Arrow functions si comportano diversamente con this.
this Binding
// Funzione classica - 'this' dipende dal contesto
const persona1 = {
nome: "Mario",
saluta: function() {
console.log(`Ciao, sono ${this.nome}`);
}
};
persona1.saluta(); // "Ciao, sono Mario"
// Arrow function - 'this' è lessicale (eredita dallo scope)
const persona2 = {
nome: "Anna",
saluta: () => {
console.log(`Ciao, sono ${this.nome}`); // 'this' non è persona2!
}
};
persona2.saluta(); // "Ciao, sono undefined"
// ⚠️ Arrow functions NON vanno bene come metodi di oggetti!
📊 Quando Usare Arrow Functions
| Situazione | Usa Arrow | Motivo |
|---|---|---|
| Callback (map, filter...) | ✅ Sì | Sintassi più breve e pulita |
| Funzioni brevi | ✅ Sì | Return implicito comodo |
| Metodi oggetto | ❌ No | Problema con 'this' |
| Costruttori | ❌ No | Non hanno 'new' |
💡 Best Practice Arrow Functions
- ✅ Usa per callback e funzioni brevi
- ✅ Preferisci return implicito quando possibile
- ❌ Evita per metodi di oggetti
- ❌ Non usare se serve
this,arguments, onew
Closures
Una closure è una funzione che "ricorda" le variabili del suo scope esterno, anche dopo che la funzione esterna è terminata. Concetto potente e fondamentale!
- Closure Base
La funzione interna accede alle variabili della funzione esterna.
Closure Semplice
function creaContatore() {
let count = 0; // Variabile "privata"
return function() {
count++;
return count;
};
}
const contatore = creaContatore();
console.log(contatore()); // 1
console.log(contatore()); // 2
console.log(contatore()); // 3
// Ogni closure mantiene il SUO stato
const contatore2 = creaContatore();
console.log(contatore2()); // 1 (nuovo contatore!)
console.log(contatore()); // 4 (primo contatore continua!)
// 'count' non è accessibile dall'esterno
// console.log(count); // ❌ ERRORE!
- Variabili Private con Closures
Simula l'incapsulamento creando variabili "private".
Variabili Private
function creaBancaAccount(saldoIniziale) {
let saldo = saldoIniziale; // Privato!
return {
deposita: function(importo) {
if (importo > 0) {
saldo += importo;
console.log(`Depositati €${importo}. Saldo: €${saldo}`);
}
},
preleva: function(importo) {
if (importo > 0 && importo <= saldo) {
saldo -= importo;
console.log(`Prelevati €${importo}. Saldo: €${saldo}`);
} else {
console.log("Fondi insufficienti!");
}
},
getSaldo: function() {
return saldo;
}
};
}
const account = creaBancaAccount(1000);
account.deposita(500); // Depositati €500. Saldo: €1500
account.preleva(200); // Prelevati €200. Saldo: €1300
console.log(account.getSaldo()); // 1300
// ❌ Non posso accedere direttamente a 'saldo'
// console.log(account.saldo); // undefined
// account.saldo = 999999; // Non funziona!
- Factory Functions con Closures
Crea funzioni personalizzate usando closures.
Factory Functions
// Factory: crea funzioni moltiplicatore
function creaMoltiplicatore(fattore) {
return function(numero) {
return numero * fattore;
};
}
const doppio = creaMoltiplicatore(2);
const triplo = creaMoltiplicatore(3);
const decuplo = creaMoltiplicatore(10);
console.log(doppio(5)); // 10
console.log(triplo(5)); // 15
console.log(decuplo(5)); // 50
// Factory: crea funzioni saluto personalizzate
function creaSaluto(saluto) {
return nome => `${saluto}, ${nome}!`;
}
const salutaItaliano = creaSaluto("Ciao");
const salutaInglese = creaSaluto("Hello");
const salutaSpagnolo = creaSaluto("Hola");
console.log(salutaItaliano("Mario")); // "Ciao, Mario!"
console.log(salutaInglese("John")); // "Hello, John!"
console.log(salutaSpagnolo("Carlos")); // "Hola, Carlos!"
Come Funzionano le Closures
Quando una funzione viene creata, porta con sé una "valigia" con tutte le variabili dello scope in cui è stata definita. Questa valigia resta accessibile anche dopo che la funzione esterna è terminata!
Callback Functions
Un callback è una funzione passata come argomento a un'altra funzione e eseguita successivamente. Pattern fondamentale in JavaScript!
- Callback Base
Passa funzioni come argomenti.
Callback Semplice
// Funzione che accetta un callback
function eseguiOperazione(a, b, operazione) {
return operazione(a, b);
}
// Callback diverse
function somma(x, y) {
return x + y;
}
function moltiplica(x, y) {
return x * y;
}
// Uso
console.log(eseguiOperazione(5, 3, somma)); // 8
console.log(eseguiOperazione(5, 3, moltiplica)); // 15
// Con arrow function inline
console.log(eseguiOperazione(10, 2, (a, b) => a - b)); // 8
console.log(eseguiOperazione(10, 2, (a, b) => a / b)); // 5
- Callback Asincroni
Molto usati con operazioni asincrone (timer, API...).
Callback Asincroni
// setTimeout - esegue callback dopo delay
console.log("Inizio");
setTimeout(function() {
console.log("Dopo 2 secondi");
}, 2000);
console.log("Fine");
// Output:
// Inizio
// Fine
// (dopo 2 secondi) Dopo 2 secondi
// Simulazione caricamento dati
function caricaDati(callback) {
console.log("Caricamento in corso...");
setTimeout(() => {
const dati = {
utente: "Mario",
età: 30,
email: "mario@example.com"
};
callback(dati);
}, 1000);
}
// Uso
caricaDati(function(dati) {
console.log("Dati caricati:", dati);
});
// Con arrow function
caricaDati(dati => {
console.log(`Utente: ${dati.utente}, Età: ${dati.età}`);
});
- Callback con Array Methods
forEach, map, filter, reduce usano callback.
Array Callbacks
const numeri = [1, 2, 3, 4, 5];
// forEach - esegue callback per ogni elemento
numeri.forEach(function(numero) {
console.log(numero * 2);
});
// map - trasforma array
const doppi = numeri.map(n => n * 2);
console.log(doppi); // [2, 4, 6, 8, 10]
// filter - filtra elementi
const pari = numeri.filter(n => n % 2 === 0);
console.log(pari); // [2, 4]
// Callback con indice
const frutti = ["mela", "banana", "arancia"];
frutti.forEach((frutto, indice) => {
console.log(`${indice + 1}. ${frutto}`);
});
// 1. mela
// 2. banana
// 3. arancia
⚠️ Callback Hell
Troppi callback annidati rendono il codice difficile da leggere. Questo problema si risolve con Promises e async/await (Promise e async/await).
// ❌ Callback Hell (piramide del destino)
step1(function(result1) {
step2(result1, function(result2) {
step3(result2, function(result3) {
step4(result3, function(result4) {
console.log(result4);
});
});
});
});
// ✅ Meglio con le Promise (vedi la lezione Promise e async/await)
step1()
.then(result1 => step2(result1))
.then(result2 => step3(result2))
.then(result3 => step4(result3))
.then(result4 => console.log(result4));
Higher-Order Functions
Una higher-order function è una funzione che:
- Accetta funzioni come parametri, O
- Restituisce una funzione, O
- Entrambe!
- Funzioni che Accettano Funzioni
Higher-Order: Accettano Funzioni
// Ripeti un'operazione n volte
function ripeti(n, azione) {
for (let i = 0; i < n; i++) {
azione(i);
}
}
// Uso
ripeti(3, i => console.log(`Iterazione ${i}`));
// Iterazione 0
// Iterazione 1
// Iterazione 2
// Applica funzione a ogni elemento array
function map(arr, trasforma) {
let risultato = [];
for (let elemento of arr) {
risultato.push(trasforma(elemento));
}
return risultato;
}
const numeri = [1, 2, 3, 4, 5];
const quadrati = map(numeri, n => n * n);
console.log(quadrati); // [1, 4, 9, 16, 25]
- Funzioni che Restituiscono Funzioni
Higher-Order: Restituiscono Funzioni
// Crea funzione checker
function creaChecker(limite) {
return function(valore) {
return valore > limite;
};
}
const maggioreDi10 = creaChecker(10);
const maggioreDi100 = creaChecker(100);
console.log(maggioreDi10(15)); // true
console.log(maggioreDi10(5)); // false
console.log(maggioreDi100(150)); // true
// Composizione funzioni
function componi(f, g) {
return function(x) {
return f(g(x));
};
}
const aggiungi5 = x => x + 5;
const moltiplica2 = x => x * 2;
const aggiungiEMoltiplica = componi(moltiplica2, aggiungi5);
console.log(aggiungiEMoltiplica(10)); // (10 + 5) * 2 = 30
- Currying
Trasforma funzione multi-parametro in catena di funzioni singolo parametro.
Currying
// Funzione normale
function somma(a, b, c) {
return a + b + c;
}
console.log(somma(1, 2, 3)); // 6
// ✨ Versione curried
function sommaCurried(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(sommaCurried(1)(2)(3)); // 6
// Con arrow functions (più compatto)
const sommaCurriedArrow = a => b => c => a + b + c;
console.log(sommaCurriedArrow(1)(2)(3)); // 6
// Uso pratico: partial application
const aggiungi10 = sommaCurriedArrow(10);
const aggiungi10E5 = aggiungi10(5);
console.log(aggiungi10E5(3)); // 18
console.log(aggiungi10E5(7)); // 22
IIFE (Immediately Invoked Function Expression)
Un IIFE è una funzione che viene eseguita immediatamente dopo essere stata definita. Utile per creare scope isolato.
- Sintassi IIFE
IIFE Base
// Funzione normale
function saluta() {
console.log("Ciao!");
}
saluta(); // Devi chiamarla
// ✨ IIFE - eseguita subito
(function() {
console.log("Ciao da IIFE!");
})();
// IIFE con parametri
(function(nome) {
console.log(`Ciao ${nome}!`);
})("Mario");
// IIFE con arrow function
(() => {
console.log("IIFE arrow!");
})();
// IIFE che restituisce valore
const risultato = (function() {
return 10 + 5;
})();
console.log(risultato); // 15
- Perché Usare IIFE
Crea uno scope privato, evita inquinamento globale.
IIFE per Privacy
// ❌ Senza IIFE - variabili globali
var counter = 0;
function increment() {
counter++;
}
// 'counter' è globale - può essere modificato da chiunque
// ✅ Con IIFE - scope privato
const counterModule = (function() {
let count = 0; // Privato!
return {
increment: function() {
count++;
console.log(count);
},
reset: function() {
count = 0;
},
getCount: function() {
return count;
}
};
})();
counterModule.increment(); // 1
counterModule.increment(); // 2
console.log(counterModule.getCount()); // 2
// console.log(count); // ❌ ERRORE! Non accessibile
💡 IIFE Oggi
Con ES6 modules, IIFE sono meno usati per l'isolamento dello scope. Tuttavia, restano utili per inizializzazioni immediate e pattern legacy.
Esempio Completo: Task Manager
Task Manager con Funzioni Avanzate
// Task Manager usando tutti i concetti
const TaskManager = (function() {
// Closure: stato privato
let tasks = [];
let nextId = 1;
// Higher-order: filtra tasks
const filterTasks = (predicate) => tasks.filter(predicate);
return {
// Aggiungi task
addTask: (description, priority = "media") => {
const task = {
id: nextId++,
description,
priority,
completed: false,
createdAt: new Date()
};
tasks.push(task);
console.log(`✅ Task aggiunto: "${description}"`);
return task;
},
// Completa task (callback)
completeTask: (id, callback) => {
const task = tasks.find(t => t.id === id);
if (task) {
task.completed = true;
console.log(`✔️ Task completato: "${task.description}"`);
if (callback) callback(task);
} else {
console.log("❌ Task non trovato");
}
},
// Lista tasks (arrow function + higher-order)
getTasks: () => tasks.slice(),
getCompletedTasks: () => filterTasks(t => t.completed),
getPendingTasks: () => filterTasks(t => !t.completed),
getByPriority: (priority) => filterTasks(t => t.priority === priority),
// Esegui azione su ogni task (callback)
forEach: (action) => {
tasks.forEach(action);
},
// Map tasks (higher-order)
map: (transform) => tasks.map(transform),
// Stats (closure + metodi)
getStats: () => ({
total: tasks.length,
completed: filterTasks(t => t.completed).length,
pending: filterTasks(t => !t.completed).length,
byPriority: {
alta: filterTasks(t => t.priority === "alta").length,
media: filterTasks(t => t.priority === "media").length,
bassa: filterTasks(t => t.priority === "bassa").length
}
}),
// Clear (con conferma callback)
clear: (onConfirm) => {
if (onConfirm && onConfirm()) {
tasks = [];
console.log("🗑️ Tutti i task eliminati");
}
}
};
})();
// === USO DEL TASK MANAGER ===
// Aggiungi tasks
TaskManager.addTask("Studiare JavaScript", "alta");
TaskManager.addTask("Fare la spesa", "media");
TaskManager.addTask("Chiamare Mario", "bassa");
TaskManager.addTask("Completare progetto", "alta");
// Completa con callback
TaskManager.completeTask(1, task => {
console.log(`Completato alle: ${new Date().toLocaleTimeString()}`);
});
TaskManager.completeTask(3);
// Filtra per priorità
console.log("\n📌 Task ad alta priorità:");
TaskManager.getByPriority("alta").forEach(t => {
console.log(` - ${t.description} ${t.completed ? "✔️" : "⏳"}`);
});
// Map: estrai solo descrizioni
const descrizioni = TaskManager.map(t => t.description);
console.log("\n📝 Tutte le descrizioni:", descrizioni);
// Stats
const stats = TaskManager.getStats();
console.log("\n📊 Statistiche:");
console.log(` Totale: ${stats.total}`);
console.log(` Completati: ${stats.completed}`);
console.log(` Pendenti: ${stats.pending}`);
console.log(` Alta priorità: ${stats.byPriority.alta}`);
// ForEach con arrow function
console.log("\n📋 Lista completa:");
TaskManager.forEach(t => {
const status = t.completed ? "✅" : "⏳";
const priority = t.priority.toUpperCase();
console.log(` ${status} [${priority}] ${t.description}`);
});
// Output:
// ✅ Task aggiunto: "Studiare JavaScript"
// ✅ Task aggiunto: "Fare la spesa"
// ✅ Task aggiunto: "Chiamare Mario"
// ✅ Task aggiunto: "Completare progetto"
// ✔️ Task completato: "Studiare JavaScript"
// Completato alle: 14:30:45
// ✔️ Task completato: "Chiamare Mario"
//
// 📌 Task ad alta priorità:
// - Studiare JavaScript ✔️
// - Completare progetto ⏳
//
// 📝 Tutte le descrizioni: ["Studiare JavaScript", "Fare la spesa", "Chiamare Mario", "Completare progetto"]
//
// 📊 Statistiche:
// Totale: 4
// Completati: 2
// Pendenti: 2
// Alta priorità: 2
//
// 📋 Lista completa:
// ✅ [ALTA] Studiare JavaScript
// ⏳ [MEDIA] Fare la spesa
// ✅ [BASSA] Chiamare Mario
// ⏳ [ALTA] Completare progetto
💡 Concetti Usati nell'Esempio
- IIFE - Modulo TaskManager auto-eseguibile
- Closure -
tasksenextIdprivati - Arrow Functions - Sintassi moderna compatta
- Callback - completeTask, forEach, clear
- Higher-Order - filter, map, forEach
- Return implicito - Arrow functions brevi
Esercizi Pratici
Esercizio 1: Debounce Function
Obiettivo: Crea funzione debounce usando closures
- Debounce ritarda l'esecuzione di una funzione
- Se chiamata di nuovo prima del delay, resetta il timer
- Utile per search input, resize events...
Mostra la soluzione
Soluzione Esercizio 1
function debounce(func, delay) {
let timeoutId; // Closure: mantiene riferimento al timer
return function(...args) {
// Cancella timer precedente
clearTimeout(timeoutId);
// Imposta nuovo timer
timeoutId = setTimeout(() => {
func(...args);
}, delay);
};
}
// Test
const salutaConDelay = debounce((nome) => {
console.log(`Ciao ${nome}!`);
}, 1000);
// Chiamate rapide
salutaConDelay("Mario"); // Cancellata
salutaConDelay("Anna"); // Cancellata
salutaConDelay("Luca"); // Questa viene eseguita dopo 1 sec
// Uso pratico: search input
const search = debounce((query) => {
console.log(`Ricerca per: "${query}"`);
// Qui farai chiamata API
}, 500);
// Simula digitazione
search("j"); // Cancellata
search("ja"); // Cancellata
search("jav"); // Cancellata
search("java"); // Eseguita dopo 500ms
Esercizio 2: Pipeline Function
Obiettivo: Crea funzione che compone più funzioni
- Accetta array di funzioni
- Restituisce funzione che le esegue in sequenza
- Output di una = input della successiva
Mostra la soluzione
Soluzione Esercizio 2
// Pipeline: esegue funzioni in sequenza
function pipeline(...funzioni) {
return function(valore) {
return funzioni.reduce((acc, fn) => fn(acc), valore);
};
}
// Funzioni di trasformazione
const aggiungi10 = x => x + 10;
const moltiplica3 = x => x * 3;
const sottrai5 = x => x - 5;
const quadrato = x => x * x;
// Crea pipeline
const trasforma = pipeline(
aggiungi10,
moltiplica3,
sottrai5,
quadrato
);
console.log(trasforma(5));
// 5 → +10 = 15 → *3 = 45 → -5 = 40 → ^2 = 1600
// Pipeline per stringhe
const maiuscola = s => s.toUpperCase();
const inverti = s => s.split("").reverse().join("");
const aggiungiPrefisso = s => `>>> ${s} <<<`;
const trasformaStringa = pipeline(
maiuscola,
inverti,
aggiungiPrefisso
);
console.log(trasformaStringa("ciao"));
// "ciao" → "CIAO" → "OAIC" → ">>> OAIC <<<"
// Versione alternativa con compose (ordine inverso)
function compose(...funzioni) {
return function(valore) {
return funzioni.reduceRight((acc, fn) => fn(acc), valore);
};
}
const trasforma2 = compose(
quadrato,
sottrai5,
moltiplica3,
aggiungi10
);
console.log(trasforma2(5)); // Stesso risultato: 1600
Esercizio 3: Memoization
Obiettivo: Ottimizza funzioni costose con cache
- Crea funzione memoize che crea cache risultati
- Se input già calcolato, restituisce dalla cache
- Testa con funzione Fibonacci
Mostra la soluzione
Soluzione Esercizio 3
// Memoization: cache risultati per performance
function memoize(fn) {
const cache = {}; // Closure: mantiene cache
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
console.log(`📦 Cache hit per: ${args}`);
return cache[key];
}
console.log(`🔄 Calcolo per: ${args}`);
const result = fn(...args);
cache[key] = result;
return result;
};
}
// Fibonacci senza memoization (lento!)
function fibonacciSlow(n) {
if (n <= 1) return n;
return fibonacciSlow(n - 1) + fibonacciSlow(n - 2);
}
// Fibonacci con memoization (veloce!)
const fibonacci = memoize(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
// Test performance
console.log("=== SENZA MEMOIZATION ===");
console.time("Slow");
console.log(fibonacciSlow(35)); // Molto lento!
console.timeEnd("Slow");
console.log("\n=== CON MEMOIZATION ===");
console.time("Fast");
console.log(fibonacci(35)); // Velocissimo!
console.timeEnd("Fast");
console.log("\n=== CHIAMATE SUCCESSIVE ===");
console.log(fibonacci(35)); // Dalla cache!
console.log(fibonacci(40)); // Usa cache parzialmente
// Altro esempio: operazione costosa
const operazioneCostosa = memoize((a, b) => {
console.log(` Calcolo ${a} ^ ${b}...`);
return Math.pow(a, b);
});
console.log("\n=== POTENZE ===");
console.log(operazioneCostosa(2, 10)); // Calcola
console.log(operazioneCostosa(2, 10)); // Cache
console.log(operazioneCostosa(3, 5)); // Calcola
console.log(operazioneCostosa(2, 10)); // Cache
// Output:
// === SENZA MEMOIZATION ===
// Slow: 215.432ms
//
// === CON MEMOIZATION ===
// 🔄 Calcolo per: 35
// (molte altre chiamate...)
// Fast: 5.821ms (molto più veloce!)
//
// === CHIAMATE SUCCESSIVE ===
// 📦 Cache hit per: 35
// 🔄 Calcolo per: 40
// (usa cache per 35, calcola solo nuovi)
🎯 Riepilogo Lezione
| Concetto | Sintassi | Uso |
|---|---|---|
| Arrow Function | () => {} |
Sintassi breve, callback |
| Closure | Funzione in funzione | Privacy, factory, memoization |
| Callback | Funzione come arg | Eventi, async, array methods |
| Higher-Order | Riceve/restituisce fn | Composizione, utilities |
| IIFE | (function(){})() |
Scope isolato, init |