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
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 bVerifica il funzionamento delle 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
Mettiamo in pratica gli operatori visti.
primi, secondi, terzi) e uniscili in uno solo usando spread.persona con nome e eta, poi creane una copia con spread. Modifica la copia e verifica che l’originale non cambia.persona ma aggiungendo una proprietà citta.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 creaContatore() è 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:
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 vedremo al bisogno.
Come sempre, MDN è un ottimo punto di partenza per esplorare tutti i metodi disponibili.
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 }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, i dataset e gli array.
Partendo dal ricettario della lezione 2, aggiungiamo le seguenti funzionalità:
<li>), aggiungiamo una classe CSS (es. .used) che evidenzia la riga selezionata (usando classList.toggle).+ e -) per modificare il numero di porzioni (default: 4).
data-base-qty con la quantità per singola porzione.<span> con una classe specifica per racchiudere solo il numero della quantità nell’HTML.Niccolò Maltoni