Come gi sappiamo, in JavaScript le funzioni sono valori.
Inoltre ogni valore ha un tipo. Di che tipo una funzione?
In JavaScript, le funzioni sono oggetti.
Un ottimo modo per pensare alle funzioni quello di immaginarle come oggetti attivi (che compiono azioni). Oltre a invocarli, possiamo trattarli come veri e propri oggetti: aggiungendo/rimuovendo propriet, passarli per riferimento.
La propriet name
Gli oggetti funzione contengono alcune propriet utili.
Ad esempio, il nome di una funzione accessible tramite la propriet name:
function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
Inoltre lassegnazione della propriet name intelligente. Funziona anche se dichiariamo la funzione per assegnazione ad una variabile:
let sayHi = function() {
alert("Hi");
};
alert(sayHi.name); // sayHi (funziona!)
Funziona anche nel caso in cui lassegnazione viene effettuata come valore di default:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (funziona!)
}
f();
Nelle specifiche, questa caratteristica viene chiamata contextual name (nome prelevato dal contesto). Se la funzione non ne fornisce uno, allora durante lassegnazione questo viene ricavato dal contesto.
Anche i metodi delloggetto possiedono la propriet name:
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
Non sta accadendo nulla di magico. Anche se ci sono dei casi in cui non c alcun modo di ricavare il nome dal contesto. In questi casi, la propriet name sar vuota, come nellesempio:
// funzione creata all'interno dell'array
let arr = [function() {}];
alert( arr[0].name ); // <stringa vuota>
// il motore non ha alcun modo di ricavare il nome corretto, per questo sar vuoto
Nella pratica per, la maggior parte delle funzioni possiedono un nome.
La propriet length
Esiste unaltra propriet molto utile, length che ritorna il numero di parametri della funzione, ad esempio:
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2
Da questo esempio possiamo notare che i parametri di resto non vengono contati.
La propriet length viene spesso utilizzata per ispezionare un funzione che opera su altre funzioni.
Ad esempio, nel codice sotto la funzione ask accetta una question da porre, e un numero arbitrario di handler (gestori) da invocare.
Una volta che lutente ha fornito la risposta, la funzione invoca gli handlers. Possiamo fornire due tipi di handlers:
- Una funzione con zero argomenti, che viene in invocata solamente nel caso in cui lutente fornisca una risposta positiva.
- Una funzione con argomenti, che viene invocata in entrambi i casi e ritorna una risposta.
Lidea quella di avere un semplice handles senza argomenti, per gestire i casi positivi (la variante pi frequente), ma siamo comunque in grado di fornire un gestore universale.
Per invocare handlers nel modo corretto, esaminiamo la propriet length:
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// per risposte positive, entrambi gli handler vengono chiamati
// per le risposte negative, solamente la seconda
ask("Question?", () => alert('You said yes'), result => alert(result));
Questo un caso particolare di quello che viene chiamato polimorfismo trattare gli argomenti in maniera differente in base al loro tipo, o nel nostro caso in base a length. Questidea ha delle utili applicazioni nelle librerie JavaScript.
Propriet aggiuntive
Possiamo anche aggiungere delle propriet.
In questo esempio aggiungiamo la propriet counter per tenere traccia delle chiamate totali:
function sayHi() {
alert("Hi");
// andiamo a contare quante volte verr invocata
sayHi.counter++;
}
sayHi.counter = 0; // valore iniziale
sayHi(); // Hi
sayHi(); // Hi
alert( `Called ${sayHi.counter} times` ); // Chiamata 2 volte
Una propriet assegnata ad una funzione, come sayHi.counter = 0 non definisce una variabile locale counter. In altre parole, una propriet counter ed una variabile let counter sono due cose separate.
Possiamo quindi trattare una funzione come un oggetto, memorizzare propriet, ma non avranno alcun effetto sullesecuzione. Le variabili non utilizzano mai le propriet della funzione e vice versa. Sono come due mondi paralleli.
Le propriet delle funzioni possono rimpiazzare le closure in alcun casi. Ad esempio, possiamo riscrivere la funzione contatore del capitolo Variable scope, closure sfruttando una propriet della funzione:
function makeCounter() {
// piuttosto di:
// let count = 0
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
Ora count viene memorizzato direttamente nella funzione, non nel Lexical Environment esterno.
Questa soluzione migliori o peggiore rispetto ad una closure?
La principale differenza che se il valore di count sta su una variabile esterna, allora il codice al suo esterno non vi pu accedere. Solamente le funzioni annidate possono modificarla. Se invece questa legata ad una funzione, possono accadere cose simili:
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
counter.count = 10;
alert( counter() ); // 10
Quindi non c alcuna scelta migliore, ogni caso va analizzato.
Named Function Expression
Named Function Expression (Espressione di Funzione con Nome), o NFE, un termine per riferirsi alle espressioni di funzioni che hanno un nome.
Ad esempio, prendiamo una normale espressione di funzione:
let sayHi = function(who) {
alert(`Hello, ${who}`);
};
E diamogli un nome:
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
Abbiamo ottenuto qualcosa? Qual lo scopo di aggiungere il nome "func"?
Innanzitutto vale la pena notare che continua ad essere un espressione di funzione. Aggiungere il nome "func" dopo function non la rende una dichiarazione di funzione, perch viene comunque creata come una parte di un assegnazione.
Quindi aggiungere un nome non provoca assolutamente nessuno danno.
La funzione rimane comunque disponibile come sayHi():
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
sayHi("John"); // Hello, John
Ci sono due cose che rendono speciale il nome func:
- Consente alla funzione di riferirsi a se stessa internamente.
- Non visibile allesterno della funzione.
Ad esempio, la funzione sayHi qui sotto, chiama nuovamente se stessa con "Guest" se non viene fornito alcun who:
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // utilizza func per richiamare se stessa
}
};
sayHi(); // Hello, Guest
// Questo non funziona:
func(); // Errore, func non definita (non visibile all'esterno)
Perch utilizziamo func? Forse potremmo semplicemente chiamare sayHi per le chiamate annidate?
In realt, in molti possibile:
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest");
}
};
Il problema con questo codice che il valore di sayHi potrebbe cambiare. La funzione potrebbe essere trasferita su unaltra variabile, e il codice diventerebbe errato:
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest"); // Errore: sayHi non una funzione
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Errore, la chiamata annidata a sayHi non pi in funzione!
Questo accade perch la funzione prende sayHi dal suo lexical environment esterno. Non c alcun sayHi locale, quindi viene utilizzata la variabile esterna. Nellesempio sopra al momento della chiamata il valore di sayHi null.
La possibilit di aggiungere un nome ad un espressione di funzione pensato proprio per risolvere questo tipo di problemi.
Sfruttiamo questa caratteristica per sistemare il codice:
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // Ora tutto okay
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Hello, Guest (la chiamata annidata funziona correttamente)
Ora funzione, perch il nome "func" locale alla funzione. Non viene prelevato dallesterno. Le specifiche garantisco che in questo modo si avr sempre un riferimento alla funzione corrente.
Il codice esterno continuer ad utilizzare la variabile sayHi o welcome. E func servir da nome interno della funzione.
La caratteristica del nome interno disponibile solamente per le espressioni di funzione, non per le dichiarazioni di funzione. Per le dichiarazioni di funzione, non c alcun modo per aggiungere un ulteriore nome interno.
Talvolta, nel caso in cui avessimo bisogno di un nome interno, potrebbe essere sensato riscrivere la dichiarazione di funzione come espressione di funzione.
Riepilogo
Le funzioni sono oggetti.
Qui abbiamo visto le propriet:
nameil nome della funzione. Non esiste solamente quando viene fornito nella definizione della funzione, ma anche nel caso di assegnazioni o propriet di un oggetto.lengthil numero di argomenti nella definizione della funzione. I parametri di resto non vengono contati.
Se una funzione viene dichiarata come espressione di funzione (non nel principale flusso di codice), e possiede un nome, questa viene definita una Named Function Expression. Il nome pu essere utilizzato internamente per auto-riferimenti, per chiamate ricorsive e altri contesti simili.
Inoltre, una funzione pu possedere diverse propriet aggiuntive. Molte librerie JavaScript fanno largo utilizzo di questa caratteristica.
Queste creano una funzione principale e ci attaccano molte altre funzioni di supporto. Ad esempio la libreria jquery definisce una funzione chiamata $. La libreria lodash definisce una funzione _. E ci aggiunge _.clone, _.keyBy e altre propriet (vedi la documentazione. In realt, lo fanno anche per diminuire la sporcizia nello spazio globale, in questo modo una libreria fornisce una sola variabile globale. Questo riduce la probabilit di conflitti tra nomi.
Quindi una funzione, oltre ad essere utile, pu fornire un insieme di altre funzionalit grazie alle propriet.
Commenti
<code>, per molte righe includile nel tag<pre>, per pi di 10 righe utilizza una sandbox (plnkr, jsbin, codepen)