Funzioni come Valori in JavaScript
- Le funzioni in JavaScript sono oggetti di prima classe, il che significa che possono essere trattate come valori.
- Possono essere assegnate a variabili, passate come argomenti e restituite da altre funzioni.
- Le funzioni possono avere proprietà personalizzate, che possono essere utilizzate per memorizzare stati o risultati.
- L'uso di funzioni come valori consente una programmazione più flessibile e modulare.
- Le funzioni possono essere destrutturate e utilizzate con parametri rest per una maggiore flessibilità.
- Le funzioni possono essere utilizzate per creare metodi generici, come
Array.sort()
, che accettano funzioni come argomenti per definire il comportamento. - Le proprietà delle funzioni possono essere utilizzate per memorizzare stati persistenti tra le invocazioni della funzione.
Funzioni come Valori
Le caratteristiche più importanti delle funzioni sono che possono essere definite e invocate. La definizione e l'invocazione di funzioni sono caratteristiche sintattiche di JavaScript e della maggior parte degli altri linguaggi di programmazione. In JavaScript, tuttavia, le funzioni non sono solo sintassi ma anche valori, il che significa che possono essere assegnate a variabili, memorizzate nelle proprietà di oggetti o negli elementi di array, passate come argomenti alle funzioni, e così via.
Per comprendere come le funzioni possano essere sia dati JavaScript che sintassi JavaScript, si consideri questa definizione di funzione:
function quadrato(x) {
return x * x;
}
Questa definizione crea un nuovo oggetto funzione e lo assegna alla variabile quadrato
. Il nome di una funzione è davvero irrilevante; è semplicemente il nome di una variabile che fa riferimento all'oggetto funzione. La funzione può essere assegnata a un'altra variabile e funzionare ancora nello stesso modo:
// Ora s fa riferimento alla stessa funzione di quadrato
let s = quadrato;
// => 16
quadrato(4);
// => 16
s(4);
Le funzioni possono anche essere assegnate alle proprietà di oggetti piuttosto che a variabili. Come si è già discusso, si chiamano le funzioni "metodi" quando si fa questo:
// Un oggetto letterale
let o = {quadrato: function(x) { return x * x; }};
// y == 256
let y = o.quadrato(16);
Le funzioni non richiedono nemmeno nomi del tutto, come quando sono assegnate a elementi di array:
// Un array letterale
let a = [x => x * x, 20];
// => 400
a[0](a[1]);
La sintassi di quest'ultimo esempio sembra strana, ma è ancora un'espressione di invocazione di funzione legale!
Come esempio di quanto sia utile trattare le funzioni come valori, considera il metodo Array.sort()
. Questo metodo ordina gli elementi di un array. Poiché ci sono molti possibili ordini per ordinare (ordine numerico, ordine alfabetico, ordine di data, crescente, decrescente, e così via), il metodo sort()
opzionalmente prende una funzione come argomento per dirgli come eseguire l'ordinamento. Questa funzione ha un compito semplice: per qualsiasi due valori che le vengono passati, restituisce un valore che specifica quale elemento verrebbe per primo in un array ordinato. Questo argomento funzione rende Array.sort()
perfettamente generale e infinitamente flessibile; può ordinare qualsiasi tipo di dati in qualsiasi ordine concepibile.
Esempio: Utilizzare le funzioni come dati
L'esempio seguente dimostra i tipi di cose che possono essere fatte quando le funzioni sono usate come valori. Questo esempio può essere un po' complicato, ma i commenti spiegano cosa sta succedendo.
// Definiamo alcune funzioni semplici qui
function somma(x, y) {
return x + y;
}
function sottrai(x, y) {
return x - y;
}
function moltiplica(x, y) {
return x * y;
}
function dividi(x, y) {
return x / y;
}
// Ecco una funzione che prende una delle funzioni precedenti
// come argomento e la invoca su due operandi
function opera(operatore, operando1, operando2) {
return operatore(operando1, operando2);
}
// Si potrebbe invocare questa funzione così per calcolare il valore (2+3) + (4*5):
let i = opera(somma, opera(somma, 2, 3), opera(moltiplica, 4, 5));
// Per il bene dell'esempio, si implementano di nuovo le funzioni semplici,
// questa volta all'interno di un oggetto letterale;
const operatori = {
somma: (x, y) => x + y,
sottrai: (x, y) => x - y,
moltiplica: (x, y) => x * y,
dividi: (x, y) => x / y,
// Questo funziona anche per le funzioni predefinite
potenza: Math.pow
};
// Questa funzione prende il nome di un operatore, cerca quell'operatore
// nell'oggetto, e poi lo invoca sugli operandi forniti. Nota
// la sintassi usata per invocare la funzione operatore.
function opera2(operazione, operando1, operando2) {
if (typeof operatori[operazione] === "function") {
return operatori[operazione](operando1, operando2);
}
else {
throw "operatore sconosciuto";
}
}
// => "ciao mondo"
opera2("somma", "ciao", opera2("somma", " ", "mondo"));
// => 100
opera2("potenza", 10, 2);
Definire Proprietà personalizzate delle Funzioni
Le funzioni non sono valori primitivi in JavaScript, ma un tipo specializzato di oggetto, il che significa che le funzioni possono avere proprietà. Quando una funzione ha bisogno di una variabile "statica" il cui valore persiste attraverso le invocazioni, è spesso conveniente utilizzare una proprietà della funzione stessa. Per esempio, si supponga di voler scrivere una funzione che restituisce un intero univoco ogni volta che viene invocata. La funzione non deve mai restituire lo stesso valore due volte. Per gestire questo, la funzione deve tenere traccia dei valori che ha già restituito, e questa informazione deve persistere attraverso le invocazioni della funzione. Si potrebbe memorizzare questa informazione in una variabile globale, ma questo è inutile, perché l'informazione è utilizzata solo dalla funzione stessa. È meglio memorizzare l'informazione in una proprietà dell'oggetto Function. Ecco un esempio che restituisce un intero univoco ogni volta che viene chiamato:
// Inizializza la proprietà counter dell'oggetto funzione.
// Le dichiarazioni di funzione sono hoisted quindi si può
// davvero fare questa assegnazione prima della dichiarazione della funzione.
interoUnico.contatore = 0;
// Questa funzione restituisce un intero diverso ogni volta che viene chiamata.
// Usa una proprietà di se stessa per ricordare il prossimo valore da restituire.
function interoUnico() {
// Restituisce e incrementa la proprietà contatore
return interoUnico.contatore++;
}
// => 0
interoUnico();
// => 1
interoUnico();
Come altro esempio, si consideri la seguente funzione fattoriale()
che usa proprietà di se stessa (trattandosi come un array) per memorizzare nella cache i risultati precedentemente calcolati:
// Calcola i fattoriali e memorizza nella cache i risultati come proprietà della funzione stessa.
function fattoriale(n) {
// Solo interi positivi
if (Number.isInteger(n) && n > 0) {
// Se non c'è risultato nella cache
if (!(n in fattoriale)) {
// Calcola e memorizza nella cache
fattoriale[n] = n * fattoriale(n - 1);
}
// Restituisce il risultato dalla cache
return fattoriale[n];
} else {
// Se l'input era errato
return NaN;
}
}
// Inizializza la cache per contenere questo caso base.
fattoriale[1] = 1;
// => 720
fattoriale(6);
// => 120; la chiamata sopra memorizza nella cache questo valore
fattoriale[5];