Funzioni
Introduzione
Le funzioni possono essere definite in due modi, ciascuno con due varianti:
Funzione standard – variante classica:
function somma(x, y) {
return x+y;
} Funzione standard – variante anonima
let f = function(x, y) {
return x+y;
}Funzione freccia (detta anche lambda)
const f = (x,y) => {
return x+y;
}Funzione freccia compatta, notare che vengono eliminate parentesi e istruzione return, questa notazione si può usare per funzioni di una riga di codice che ritornano un valore.
const f = x,y => x+yLe tipologie di funzione sono praticamente identiche (in realtà esistono delle differenze, ma in questo contesto sono irrilevanti).
Le funzioni sono oggetti (in gergo tecnico sono “first class objects”) e vanno trattate come qualsiasi altro tipo di dato.
- è possibile salvare una funzione in una variabile;
- è possibile inserire una funzione in un array o un dizionario;
- si possono realizzare funzioni che restituiscono funzioni;
- si possono passare funzioni come parametro di altre funzioni.
Unica cosa non possibile, non possono essere trasformate in stringa, quindi sono escluse dalla loro trasformazione in CSV o JSON.
Funzioni autoeseguenti
E’ possibile scrivere funzioni anonime che si auto eseguono nello script principale. Il vantaggio delle funzioni autoeseguenti è che evitano di utilizzare variabili globali nello script, ma dipendono solo dai parametri della funzione. Ad esempio poniamo di avere questo script di pagina:
const data = [1,2,3,4,5];
data.forEach((element) => {
console.log(element);
};Questo codice prevede una variabile data che sarà visibile a tutte le funzioni presenti nella web application, col risultato che potranno globalmente vederla e modificarla, anche per errore.
Molto meglio scrivere una funzione autoeseguente anonima, che consiste nel racchiudere il codice dello script una funzione anonima tra parentesi tonde e subito eseguirla passando i parametri tra parentesi tonde. Vediamo un esempio:
((data) => {
data.forEach((element) => {
console.log(element);
};
})([1,2,3,4,5]);In questo caso non c’è modo di vedere il contenuto di data dall’esterno di questa funzione.
Funzioni come parametro
Le funzioni possono essere passate come parametri ad un’altra funzione. Ad esempio:
const sum = a,b => a+b;
const mult = a,b => a*b;
const myFunction = (value1, value2, operation) => {
const result = operation(value1, value2);
return result;
}
let mySum = myFunction(3,5,sum);
let myMult = myFunction(6,5,mult);
Quando occorre scrivere diverse funzioni molto simili tra loro, che differiscono solo per una parte del loro codice, risulta più conveniente creare una funzione contenitore, che contiene la parte comune, e poi eseguire la parte che diverge in una funzione specifica, che appunto viene ricevuta come parametro.
Questo principio di design viene usato anche in molte funzioni di libreria, come sort, map, filter, reduce ecc.
Come vedremo lo stesso meccanismo viene implementato per gestire il “cosa fare dopo” che una funzione ha svolto la sua attività. Questo meccanismo viene chiamato callback: una funzione riceve dei parametri ed una funzione callback. La funzione svolge l’attività per cui è progettata, ed al termine esegue la funzione ricevuta come parametro, detta appunto callback.
Currying
Le funzioni possono essere restituite come risultato della funzione. Qui un esempio:
const createSearch = (list) => {
const filtered = list.filter(element => element !== null || element !== undefined);
return (searchString) => {
return filtered.first(element => element.find(searchString) !== -1);
}
}
const search = createSearch(["Mario", null, "Maria", "Sergio");
console.log(search("Mario"));
La funzione createSearch prima di tutto filtra la lista da elementi nulli o indefiniti. Poi restituirà una funzione di ricerca che cercherà nella lista filtrata.
Questa operazione viene chiamata “currying“. Il currying può essere usato come nell’esempio precedente per svolgere delle operazioni preliminari su un set di dati, che possono essere poi utilizzate per utilizzi successivi della funzione restituita, senza doverli rifare ogni volta. La funzione createSearch svolge una esecuzione parziale, e poi mette a disposizione la funzione definitiva.
E’ da osservare che la variabile filtered non viene cancellata quando createSearch si conclude. Infatti la porzione di stack di memoria rimane attiva perché esiste ancora un oggetto (la funzione search) che continua ad usarlo. Questo tipo di comportamento si chiama closure e lo vedremo meglio sotto.
Closure
Le closure sono funzioni che generano oggetti (o come visto sopra funzioni) e che mantengono nella memoria delle variabili private utilizzabili dall’oggetto restituito, ma non visibili nè modificabili dall’esterno.
const createAdder = () => {
let sum = 0;
return {
sum: (value) => sum += value,
show: () => console.log(sum)
}
}
const adder = createAdder();
adder(3); // aggiunge 10
adder(5); // aggiunge 5
adder(4); // aggiunge 4
adder.show(); // stampa la somma 19La funzione che abbiamo creato crea delle variabili che restano in memoria ma che continuano a persistere anche dopo che la funzione createAdder ha terminato. Queste variaili sono però inaccessibili dall’esterno e l’unico modo per accedervi è usare le funzioni dell’Tuttavia sum è invisibile all’esterno e quindi non è possibile modificarne il valore direttamente, ma solo agire tramite la funzione adder.
Le closure sono un meccanismo efficace per memorizzare in modo protetto variabili senza che siano accessibili dall’esterno. Con questo sistema si ottiene l’incapsulazione, cioè la creazione di oggetti Javascript che possono accedere ai propri dati in modo esclusivo.
Il loro utilizzo è fondamentale per creare applicazioni Javascript basate sulla programmazione ad oggetti, dove ogni oggetto non accede nè a variabili globali, nè a variabili di altri oggetti, in modo da rendere possibile evitare errori di programmazione, riusare il codice e semplificare in generale la progettazione dell’applicazione.
