Una funzione è un blocco di codice che esegue un compito specifico. Abbiamo già visto esempi di funzioni integrate nel linguaggio, come alert(message) e console.log(value).
Per creare una funzione dobbiamo utilizzare una dichiarazione di funzione:
La parola chiave function va posta all’inizio; viene seguita da:
L’istruzione return interrompe l’esecuzione della funzione e restituisce il valore.
return restituisce undefined.Attenzione
È anche possibile specificare return senza un valore: in questo caso la funzione restituirà undefined, come se non avesse un’istruzione return.
In JavaScript, però, è possibile terminare la riga con un punto e virgola (;) o andare a capo. Se si va a capo subito dopo return, JavaScript inserirà automaticamente un punto e virgola, causando il ritorno di undefined!
Funzioni che ritornano valori
Apri la console del browser e crea 4 funzioni:
somma(a, b) che ritorna la somma di a e bsottrai(a, b) che ritorna la sottrazione di b da amoltiplica(a, b) che ritorna il prodotto di a e bdividi(a, b) che ritorna la divisione di a per bTesta le funzioni:
Esiste un’altra sintassi, più concisa, per creare funzioni e che spesso è preferibile a function.
Questa notazione è chiamata arrow function per via della freccia => (composta dai simboli uguale e maggiore) usata tra i parametri e il corpo della funzione.
Le arrow functions possiedono altre caratteristiche, che vedremo più avanti.
Se abbiamo un solo argomento, le parentesi che racchiudono gli argomenti possono essere omesse, abbreviando ulteriormente il codice.
Se non ci sono argomenti, le parentesi saranno vuote (ma devono essere presenti).
Se il corpo della funzione è una singola espressione, le parentesi graffe e la parola chiave return possono essere omesse: il valore dell’espressione sarà restituito automaticamente (implicit return).
Lo scope determina dove una variabile è accessibile.
Le variabili dichiarate con let o const dentro una funzione non si vedono da fuori.
Viceversa, le variabili dichiarate fuori da una funzione sono considerate globali e utilizzabili anche all’interno della funzione.
Una funzione ricorsiva è una funzione che chiama se stessa.
La ricorsione è utile per problemi che si ripetono su dati più piccoli (alberi, liste, calcoli matematici).
In JavaScript, le funzioni sono first-class citizen!
Possono essere:
Una callback è una funzione passata come argomento a un’altra funzione.
Le callback sono fondamentali per lavorare con operazioni asincrone, che vedremo più avanti.
Callbacks in azione
Scrivi una funzione applicaOperazione(numeri, operazione) che:
Poi crea due callback:
raddoppia(n) che ritorna n * 2quadrato(n) che ritorna n * nTesta:
JavaScript non controlla il numero di parametri passati a una funzione.
Per evitare undefined, possiamo specificare i valori di default da utilizzare:
Anche le arrow functions supportano i default:
...argsCon i rest parameters, possiamo accettare un numero variabile di argomenti come array:
Possiamo anche combinare parametri normali con rest:
A differenza di arguments, i rest parameters sono veri array e supportano tutti i metodi degli array.
Inoltre, può essere utilizzato anche nelle arrow functions.
...arrayLo spread operator (operatore espansione) è di fatto il contrario di rest: espande un array in singoli elementi.
Lo spread operator è utilizzabile ogniqualvolta si ha a disposizione un oggetto iterabile come un array, non necessariamente in un contesto di chiamata di funzione.
Ad esempio, è molto utile per concatenare array:
Spread in pratica
Apri la console e prova:
Unire array: crea 3 array (primi, secondi, terzi) e uniscili in uno solo usando spread.
Copiare oggetto: crea un oggetto persona con nome e eta, poi creane una copia con spread. Modifica la copia e verifica che l’originale non cambia.
Aggiungere proprietà: usa spread per creare un nuovo oggetto partendo da persona ma aggiungendo una proprietà citta.
Esempio atteso:
In JavaScript, lo scope è lessicale. Questo significa che le regole che determinano la visibilità degli identificatori dipendono solo da come il codice è scritto, e non da ciò che avviene un fase di esecuzione.
Di fatto, le funzioni “vedono” le variabili del contesto in cui sono definite, non dove sono chiamate.
Una closure (chiusura) è una funzione che ricorda le sue variabili esterne ed è in grado di accedervi. In alcuni linguaggi questo non è possibile, oppure è richiesto che la funzione venga scritta in un determinato modo. In JavaScript, tutte le funzioni sono closure di natura.
La funzione ricorda la variabile contatore, anche se creatoreContatore() è finita.
Ogni closure mantiene il suo proprio moltiplicatore.
function creaBanca(soldi) {
return {
deposita: function(importo) {
soldi += importo;
return soldi;
},
preleva: function(importo) {
if (importo <= soldi) {
soldi -= importo;
}
return soldi;
},
saldo: function() {
return soldi;
}
};
}
const mioBancomat = creaBanca(1000);
mioBancomat.deposita(200); // 1200
mioBancomat.preleva(500); // 700
console.log(mioBancomat.saldo()); // 700
console.log(mioBancomat.soldi); // undefined, non possiamo accedere direttamente a `soldi`Closure per incapsulamento
Crea una funzione creaContatore(valoreIniziale) che restituisce un oggetto con questi metodi:
incrementa() - aumenta il contatore di 1 e lo restituiscedecrementa() - diminuisce il contatore di 1 e lo restituiscereset() - riporta il contatore al valore inizialegetValue() - restituisce il valore corrente senza modificarloLa variabile del contatore deve essere privata (accessibile solo tramite i metodi).
Test:
Suggerimento: usa una closure per mantenere il valore privato, come nell’esempio della banca.
Nella lezione 2 abbiamo visto le basi di array e oggetti.
Ora vediamo i metodi più utili per lavorare con gli array in modo funzionale.
forEach)map)filter)reduce)Gli altri metodi (find, some, every, sort, reverse) li useremo quando servono negli esercizi.
forEach: iterare su ogni elementoforEach esegue una funzione per ogni elemento dell’array:
Nota: forEach esegue solo azioni, non restituisce nulla. Per trasformare o filtrare array, usiamo map e filter.
map e filtermap: trasformare elementimap crea un nuovo array trasformando ogni elemento:
const numeri = [1, 2, 3, 4, 5];
// Raddoppia ogni numero
const raddoppiati = numeri.map(n => n * 2);
console.log(raddoppiati); // [2, 4, 6, 8, 10]
// Estrai nomi da oggetti
const persone = [
{ nome: 'Mario', eta: 30 },
{ nome: 'Luigi', eta: 28 },
{ nome: 'Anna', eta: 32 }
];
const nomiEstratti = persone.map(p => p.nome);
console.log(nomiEstratti); // ['Mario', 'Luigi', 'Anna']filter: filtrare elementifilter crea un nuovo array con solo gli elementi che soddisfano una condizione:
reduce riduce un array a un singolo valore, usando un accumulatore:
Come funziona?
acc = 0 (valore iniziale)0 + 1 = 1, poi 1 + 2 = 3, poi 3 + 3 = 6, ecc.15// Moltiplicazione
const prodotto = numeri.reduce((acc, n) => acc * n, 1);
console.log(prodotto); // 120
// Creare un oggetto conteggio
const colori = ['rosso', 'blu', 'rosso', 'verde', 'blu', 'rosso'];
const conteggio = colori.reduce((acc, colore) => {
acc[colore] = (acc[colore] || 0) + 1;
return acc;
}, {});
console.log(conteggio); // { rosso: 3, blu: 2, verde: 1 }Oltre al nucleo map/filter/reduce, è utile conoscere anche:
find e findIndex per cercare elementi;some e every per verifiche booleane;sort e reverse per ordinare;.filter(...).map(...)) per combinare passaggi.Li useremo in modo mirato negli esercizi successivi, senza approfondirli tutti ora.
Come abbiamo visto, JavaScript è un linguaggio multi-paradigma orientato agli eventi (event-driven). Ma cosa sono esattamente gli eventi?
Un evento è un segnale che sta ad indicare che è avvenuto qualcosa:
Tutti i nodi DOM generano questi segnali; inoltre, gli eventi non sono limitati al DOM.
click: quando si clicca col mouse su un elemento (i dispositivi touch lo generano tramite il tocco).contextmenu: quando si clicca col tasto destro su un elemento.mouseover e mouseout: quando il cursore passa sopra o abbandona un elemento.mousedown e mouseup: quando viene premuto o rilasciato il pulsante del mouse su un elemento.mousemove: quando si sposta il mouse.keydown e keyup: quando viene premuto e rilasciato un tasto.Document:
DOMContentLoaded: quando l’HTML viene caricato e la costruzione del DOM è stata completata.transitionend: quando termina un’animazione CSS.Come sempre, Mozilla e W3Schools offrono ottime risorse per esplorare tutti gli eventi disponibili.
Gli eventi da soli non fanno nulla, dobbiamo collegare una funzione che reagisca quando l’evento accade. Questa funzione è chiamata event handler (gestore dell’evento) o event listener.
Possiamo collegare un handler in tre modi:
Un gestore può essere impostato con un attributo HTML chiamato on<evento>, ad esempio:
Oppure, possiamo assegnare una proprietà dell’elemento DOM chiamata on<evento>, ad esempio:
Infine, possiamo utilizzare il costrutto moderno addEventListener.
addEventListener: ascoltare gli eventiIl problema dei primi due metodi è che possono essere usati solo una volta per ogni evento su un elemento. Se ne assegniamo un secondo, sovrascrive il primo.
addEventListener permette di aggiungere più gestori allo stesso evento senza sovrascrivere:
La nostra funzione handler deve essere passata come riferimento, non invocata:
function saluta() {
alert('Ciao!');
}
const bottone = document.querySelector('button');
// Corretto: passiamo la funzione senza parentesi
bottone.addEventListener('click', saluta);
// Errato: stiamo invocando la funzione subito, invece di passarla come riferimento
bottone.addEventListener('click', saluta());EventPer gestire correttamente un evento, vorremmo saperne di più su cosa è avvenuto.
A questo scopo, quando si verifica un evento, il browser crea un oggetto evento (event object) inserisce i dettagli al suo interno e lo passa come argomento alla funzione handler.
Alcune proprietà dell’oggetto event sono:
event.type: tipo di eventoevent.target: l’elemento più interno su cui è avvenuto l’eventoevent.currentTarget: l’elemento che ha gestito l’evento (ovvero su cui è registrato il listener)event.clientX e event.clientY: coordinate del cursore relative alla finestraevent.timeStamp: istante (in millisecondi) in cui l’evento è avvenutoMolti eventi vengono gestiti direttamente dal browser (es. <form> ricarica la pagina, <a> naviga); è possibile bloccare queste gestioni di default e sostituirle con altri comportamenti.
Il modo migliore è usando il metodo event.preventDefault():
Un’alternativa, se l’handler viene assegnato tramite on<event>, è restituire false:
Quest’ultima modalità non funziona con addEventListener ed è generalmente sconsigliata.
Quando viene innescato un evento su un elemento:
const parent = document.querySelector('.parent');
const button = document.querySelector('button');
parent.addEventListener('click', () => console.log('Parent cliccato'));
button.addEventListener('click', () => console.log('Button cliccato'));
// Click button → stampa: "Button cliccato", poi "Parent cliccato"Questa dinamica è chiamata event bubbling (propagazione dell’evento) ed è utile per gestire eventi su più elementi senza dover assegnare un listener a ciascuno.
Se necessario, possiamo interrompere il bubbling con event.stopPropagation(), ma è una pratica da usare con cautela.
Invece di assegnare un listener a ogni elemento, possiamo usarne uno solo sul contenitore:
script.js
Evoluzione del ricettario
Nella lezione precedente abbiamo costruito il ricettario usando HTML, CSS e JavaScript per iniettare il contenuto nel DOM.
Adesso aggiungiamo interattività usando gli eventi che abbiamo imparato.
Partendo dal ricettario della lezione 2, aggiungiamo le seguenti funzionalità:
Evidenziazione di ingredienti: quando l’utente clicca su un ingrediente (elemento <li>), aggiungete una classe CSS (es. .usato) che cambia il colore di background o aggiunge un’icona (es. una spunta).
Contatore di ingredienti usati: create una variabile (con closure) che traccia quanti ingredienti sono stati marcati come “usati”. Mostrate il conteggio in una <div> sotto la lista.
Filtrare ingredienti per tipo: se gli ingredienti hanno un attributo data-tipo (es. data-tipo="verdure", data-tipo="pasta"), aggiungete due bottoni per mostrare/nascondere solo ingredienti di quel tipo, usando filter() e classList.toggle().
Button “Preferito”: aggiungete un bottone che salva/rimuove la ricetta dalla lista dei preferiti. Usate una closure per tracciare quali ricette sono preferite.
Niccolò Maltoni