Funzioni Avanzate
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.
- ✅ 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!
Diversi modi di scrivere arrow functions.
// 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!"
Se la funzione ha una sola espressione, il return è automatico!
// ✅ 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!
Arrow functions si comportano diversamente con this.
// 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!
| 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’ |
- ✅ 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!
La funzione interna accede alle variabili della funzione esterna.
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!
Simula l’incapsulamento creando 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!
Crea funzioni personalizzate usando closures.
// 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!
Passa funzioni come argomenti.
// 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
Molto usati con operazioni asincrone (timer, API...).
// 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à}`);
});
forEach, map, filter, reduce usano callback.
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
Troppi callback annidati rendono il codice difficile da leggere.
Questo problema si risolve con Promises e async/await
(Lezione 16).
// ❌ 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 Promises (Lezione 16)
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!
// 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]
// 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
Trasforma funzione multi-parametro in catena di funzioni singolo parametro.
// 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.
// 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
Crea uno scope privato, evita inquinamento globale.
// ❌ 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
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 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
- IIFE - Modulo TaskManager auto-eseguibile
- Closure - `tasks` e `nextId` privati
- 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...
📝 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
📝 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
| 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 |
Lezione 10 del Corso JavaScript Completo