Invocare una Funzione in JavaScript
Il codice JavaScript che costituisce il corpo di una funzione non viene eseguito quando la funzione è definita, ma piuttosto quando viene invocata.
Le funzioni JavaScript possono essere invocate in cinque modi:
- Come funzioni
- Come metodi
- Come costruttori
- Indirettamente attraverso i loro metodi
call()
eapply()
- Implicitamente, tramite caratteristiche del linguaggio JavaScript che non appaiono come normali invocazioni di funzioni
In questa lezione ci dedichiamo al primo caso.
- Le funzioni vengono invocate con un'espressione di invocazione che valuta un oggetto funzione seguito da una parentesi aperta, una lista di argomenti e una parentesi chiusa.
- Il contesto di invocazione determina il valore di
this
all'interno della funzione. - Le funzioni possono essere invocate in modo condizionale usando l'operatore
?.
. - Le funzioni ricorsive possono causare un errore di stack overflow se chiamate troppe volte.
Invocazione di Funzioni
Le funzioni vengono invocate come funzioni o come metodi con un'espressione di invocazione.
Un'espressione di invocazione consiste in un'espressione di funzione che valuta un oggetto funzione seguito da una parentesi aperta, una lista separata da virgole di zero o più espressioni di argomenti e una parentesi chiusa. Se l'espressione di funzione è un'espressione di accesso a proprietà, ossia se la funzione è la proprietà di un oggetto o un elemento di un array, allora è un'espressione di invocazione di metodo.
Esaminiamo il caso di invocazione di funzione regolare, in cui l'espressione di funzione è un nome di funzione. In questo caso, l'invocazione di funzione è un'espressione che valuta il valore della funzione e lo invoca con gli argomenti specificati.
stampaProprieta({x: 1});
let totale = distanza(0,0,2,1) + distanza(2,1,3,5);
let probabilita = fattoriale(5)/fattoriale(13);
In un'invocazione, ogni espressione di argomento (quelle tra le parentesi) viene valutata, e i valori risultanti diventano gli argomenti alla funzione. Questi valori vengono assegnati ai parametri nominati nella definizione della funzione. Nel corpo della funzione, un riferimento a un parametro valuta il valore dell'argomento corrispondente.
Per l'invocazione di funzioni regolari, il valore di ritorno della funzione diventa il valore dell'espressione di invocazione. Se la funzione ritorna perché l'interprete raggiunge la fine, il valore di ritorno è undefined
. Se la funzione ritorna perché l'interprete esegue una dichiarazione return
, allora il valore di ritorno è il valore dell'espressione che segue il return
o è undefined
se la dichiarazione return
non ha valore.
Invocazione Condizionale
A partire da ES2020 si può inserire ?.
dopo l'espressione della funzione e prima della parentesi aperta in un'invocazione di funzione per invocare la funzione solo se non è null
o undefined
. Cioè, l'espressione f?.(x)
è equivalente (assumendo che non abbia nessun effetto collaterale) a:
// Invocazione condizionale di funzione
f?.(x);
// Espressione equivalente
(f !== null && f !== undefined) ? f(x) : undefined
Per l'invocazione di funzione in modalità non-strict, il contesto di invocazione (il valore this
) è l'oggetto globale. In modalità strict, tuttavia, il contesto di invocazione è undefined
. Si noti che le funzioni definite utilizzando la sintassi arrow si comportano diversamente: ereditano sempre il valore this
che è in vigore dove sono definite.
Le funzioni scritte per essere invocate come funzioni (e non come metodi) tipicamente non usano affatto la parola chiave this
. La parola chiave può essere usata, tuttavia, per determinare se la modalità strict è in vigore:
// Definisce e invoca una funzione per determinare
// se siamo in modalità strict.
const strict = (function() { return !this; }());
Funzioni Ricorsive e lo Stack
Una funzione ricorsiva è una funzione che chiama se stessa.
L'esempio classico è la funzione che calcola il fattoriale di un numero:
function fattoriale(n) {
if (n === 0) {
return 1;
}
return n * fattoriale(n - 1);
}
Alcuni algoritmi, come quelli che coinvolgono strutture dati basate su alberi, possono essere implementati in modo particolarmente elegante con funzioni ricorsive. Quando scriviamo una funzione ricorsiva, tuttavia, è importante pensare ai vincoli di memoria.
Quando una funzione A
chiama la funzione B
, e poi la funzione B
chiama la funzione C
, l'interprete JavaScript ha bisogno di tenere traccia dei contesti di esecuzione per tutte e tre le funzioni. Quando la funzione C
si completa, l'interprete ha bisogno di sapere dove riprendere l'esecuzione della funzione B
, e quando la funzione B
si completa, ha bisogno di sapere dove riprendere l'esecuzione della funzione A
.
Possiamo immaginare questi contesti di esecuzione come uno stack. Quando una funzione chiama un'altra funzione, un nuovo contesto di esecuzione viene spinto sullo stack. Quando quella funzione ritorna, il suo oggetto del contesto di esecuzione viene rimosso dallo stack. Se una funzione chiama se stessa ricorsivamente 100 volte, lo stack avrà 100 oggetti spinti su di esso, e poi avrà quei 100 oggetti rimossi. Questo call stack richiede memoria. Su hardware moderno, è tipicamente accettabile scrivere funzioni ricorsive che chiamano se stesse centinaia di volte. Ma se una funzione chiama se stessa diecimila volte, è probabile che fallisca con un errore come Maximum call-stack size exceeded
.