Le funzioni Javascript | NEXT chapters | ||||
Il numero fattoriale di un numero naturale è il prodotto di tutti i numeri positivi precedenti o uguali al numero di partenza. Il fattoriale di 4, per esempio, è dato da:
La funzione fac(), assegnata alla variabile factorial prende il numero di partenza ( n ), ricevuto dal bottone HTML Calcola ( 1, 2, 3, 4, etc. ) ed esegue tutte le moltiplicazioni necessarie al calcolo del numero fattoriale, invocando se stessa all'interno di se stessa:
Se volete vedere tutte le moltiplicazioni effettuate, inserite un alert anche all'interno della funzione fact(), in modo da vedere tutti i valori assunti da n:
Funzioni come parametri di altre funzioni Il prossimo esempio mostra una funzione map che viene definita e poi invocata, con una funzione anonima come primo parametro. La funzione map:
La funzione map riceve due argomenti: f e a. Dal corpo della funzione, possiamo intuire che l'argomento a potrebbe essere un array, visto che troviamo la proprietà length associata ad esso:
all'interno di un loop for, mentre l'argomento f è sicuramente una funzione, visto che va eseguita su ogni elemento di a:
La funzione f viene invocata dal bottone "Invoca":
di cui il primo argomento è proprio una funzione anonima:
mentre il secondo argomento è proprio un array di valori su cui eseguire la funzione anonima:
Per vedere i risultati della funzione anonima eseguita sui valori contenuti nell'array, premete il bottone "Invoca":
Alle variabili definite all'interno di una funzione non è possibile accedere dall'esterno della funzione, poichè la variabile è definita solo per lo scope della funzione ( scope: portata, ambiente, ambito in cui il nome della variabile può essere utilizzato per riferirsi ad essa ). Tuttavia, una funzione può accedere a tutte le variabili e a tutte le funzioni definite all'interno dello scope in cui è stata definita. In altre parole, una funzione definita nello scope globale può accedere a tutte le variabili definite nello scope globale. Una funzione definita all'interno di un'altra funzione può accedere a tutte le proprie variabili, a tutte le variabili definite nella funzione genitrice ( parent ) e a tutte le altre variabili alle quali può accedere la funzione genitrice. Vediamo un esempio:
La funzione multiply() conosce i valori delle variabili num1 e num2, poichè sono state dichiarate nello stesso scope in cui è stata definita la funzione. Ora, annidiamo una funzione:
La funzione getScore () è definita nello scope globale: quindi, i valori delle variabili num1 e num2 vengono modificati nei valori precedentemente assegnati. La funzione add(), invece, è annidata nella funzione getScore () di cui condivide lo scope: ecco perchè la funzione add() conosce anche il valore della variabile name: la variabile name, infatti, è stata definita nello scope globale ed è raggiungibile anche dalla funzione getScore () e, quindi, anche dalla funzione annidata add(), che è contenuta in getScore () ( Funzioni Javascript ).
La funzione sum() è la funzione esterna o funzione genitrice, mentre la funzione square() è la funzione annidata o interna. Alla funzione interna possono accedere solo le istruzioni contenute nella funzione esterna:
Sappiamo che le variabili dichiarate all'interno di una funzione hanno uno scope solo locale: una volta chiusa la funzione, vengono distrutte, con la deallocazione dello spazio di memoria assegnato alla funzione ( stack ). Una funzione interna può utilizzare gli argomenti e le variabili della funzione esterna, ma la funzione esterna non può utilizzare gli argomenti e le variabili della funzione interna. Perchè la funzione interna ( annidata ) è privata, rispetto alla funzione che la contiene (outer o esterna). Esistono solo due modi per rendere disponibili le variabili di una funzione interna alla funzione esterna genitrice: l'istruzione return e la chiusura. L'istruzione return, che chiude sempre una funzione, interna o esterna che sia, restituisce uno o più valori della funzione appena eseguita alla funzione o all'espressione di funzione che l'aveva invocata ( chiamante, esterna ). Modifichiamo uno dei nostri ultimi esempi, dove avevamo una funzione annidata add():
che ora restituisce ( return ), alla funzione getScore2(), che lo salva nella variabile a, il valore della variabile locale tot, variabile di cui, altrimenti, non resterebbe alcuna traccia, una volta completata la funzione. La chiusura, invece, è una tecnica che permette di dichiarare il valore di una variabile libera ( una variabile usata all'interno della funzione annidata, ma definita nello scope superiore, in cui la funzione è stata generata, proprio come le variabili num1 e num2 del nostro ultimo esempio ), ed associarlo ad una funzione esterna alla funzione annidata, così da poterne garantire la persistenza attraverso molteplici chiamate. "Una chiusura è un record, in cui vengono memorizzate una funzione e le variabili libere della funzione, con il valore o la locazione di memoria associato a ciascuna variabile nel momento in cui è stata creata la chiusura ... Una chiusura, quindi, è un'istanza di una funzione, le cui variabili non locali sono state associate a determinati valori ( Wikipedia ). Vediamo un esempio:
La funzione annidata incrementBy(y) eredita lo scope della funzione genitrice, startAt(x), ereditandone, quindi, anche gli argomenti e le variabili ( di cui stampiamo i valori, grazie all'alert() ). Visto che la funzione startAt(x) restituisce ( return ) una chiusura, contenente la funzione incrementBy(y), è possibile invocare la funzione esterna, startAt(x), specificando gli argomenti di entrambe le funzioni, quella esterna e quella interna. La funzione startAt(x) creerà, quindi, un riferimento alla variabile x, in modo che incrementBy(y) sappia dove reperire quel valore:
In alternativa, è possibile fissare ( bind ) il valore della variabile x ad una istanza della funzione esterna startAt(x), creando una chiusura:
La variabile closure ( che potremmo chiamare con un qualsiasi altro nome, ma qui chiamiamo closure per sottolineare che con questa dichiarazione viene creata la chiusura ), ora, contiene la funzione incrementBy(y):
che, ora, aspetta solo il valore di y, poichè x è stata già dichiarata. La funzione incrementBy(y) verrà eseguita sempre con il valore di x pari a 4, fino alla distruzione della variabile closure e a condizione che venga invocata con la funzione closure(y):
Come potete verificare, il valore della variabile x sopravvive alla chiusura della funzione annidata, poichè la variabile closure è stata definita e dichiarata al di fuori del suo scope. Da questi esempi, è chiaro che le istruzioni:
chiamano la stessa funzione, incrementBy(), mentre gli ambienti associati a ciascuna istruzione differiscono: invocando le quattro chiusure, quindi, la variabile x verrà associata a quattro variabili y distinte, ciascuna delle quali ha un differente valore, per ciascuna delle quattro chiamate, generando, di conseguenza, quattro differenti risultati. Inoltre, le variabili x e y restano variabili private, non modificabili direttamente dallo scope globale: la nuova funzione closure, infatti, viene creata nello scope globale, ma resta associata al valore di x assegnato in fase di dichiarazione ( 4 ). Il solo modo di modificare il valore di x, quindi, è di creare una nuova istanza della funzione genitrice, o creando una variabile funzione closure2, oppure invocando direttamente la funzione startAt(x), trasmettendo i due nuovi valori di x e y. La variabile funzione closure, invece, conserverà, fino alla sua distruzione, il valore di x pari a 4. "L'implementazione delle variabili libere legate in una chiusura richiede un trattamento diverso dalle normali variabili che molti linguaggi mantengono su uno stack lineare. Infatti lo stack viene liberato quando si ritorna da una invocazione, mentre le variabili libere di una chiusura devono sopravvivere. Pertanto tali variabili devono essere allocate diversamente in modo da persistere finché non siano più utilizzabili. Di solito quindi le variabili della chiusura sono allocate nello heap e si fa ricorso alla garbage collection per deallocare la chiusura" ( Wikipedia ). Un esempio illuminante di quanto utile possa essere la persistenza del valore di una variabile privata, attraverso più chiamate alla stessa funzione, è riportato alla pagina JavaScript Closures, dove viene impostato un contatore che viene inizializzato una sola volta, per poi essere incrementato di uno ad ogni chiamata successiva:
Provate, ora, a cliccare più volte il bottone add() e scoprirete che ogni volta il contatore si incrementerà di 1. La variabile add contiene la funzione anonima annidata di incremento:
poichè la funzione è il valore restituito dalla funzione anonima principale ( genitrice della funzione anonima annidata ). Nel corso della prima esecuzione della funzione anonima principale, eseguita, al caricamento della pagina che state leggendo, per assegnare un valore alla variabile add, è stata anche eseguita la dichiarazione della variabile counter:
la quale dichiarazione non verrà mai più ripetuta nel corso delle successive esecuzioni della funzione add(), proprio perchè la funzione add() esegue solo la funzione anonima annidata. Da notare che la sola alternativa, per rendere persistente il valore di counter, è di inizializzare la variabile nello scope globale:
Le funzioni possono essere annidate a più livelli. Per esempio, una funzione (A), può contenere una funzione (B), che può contenere, a sua volta, una funzione (C). Entrambe le funzioni B e C formano una chiusura, qui, così B può accedere ad A e C può accedere a B. Inoltre, visto che C può accedere a B che può accedere ad A, C può anche accedere ad A. Quindi, le chiusure possono contenere più scope; ciascuna chiusura contiene lo scope delle funzioni che la contengono. Questo meccanismo è chiamato scope chaining ( catena di scope ). Considerate l'esempio seguente:
In questo esempio, C accede alla variabile y di B e alla x di A. Questo accade perchè:
Il contrario, tuttavia, non è vero. A non può accedere a C, perchè A non può accedere ad alcun argomento o variabile di B, di cui C è una variabile. Quindi, C resta privata solo a B ( Funzioni Javascript ). Una chiusura viene creata quando la funzione interna viene resa disponibile in qualche modo agli scope esterni alla funzione esterna. Il campo di applicazione che, forse, più utilizza il meccanismo Javascript delle chiusure, è la programmazione ad oggetti. Cos'è un oggetto, se non un'istanza di una funzione? Vediamo come creare un oggetto, contenente metodi per manipolare le variabili interne della funzione esterna:
Copiate questo codice e caricate la pagina: si apriranno tre finestre alert: la prima conterrà il nome Vivie, la seconda conterrà l'aggettivo male, la terza conterrà il nome Oliver. Perchè? La funzione esterna, createPet(), che viene eseguita immediatamente, crea un oggetto pet, salvato nella variabile globale pet. L'oggetto pet contiene due attributi: name, al quale viene immediatamente assegnato il valore Vivie, e sex, che resta senza valore fino alla chiamata del metodo:
e quattro metodi:
Grazie ai metodi setName e setSex, invocati all'esterno della funzione esterna, vengono, in seguito, impostate o modificate le variabili interne. Non c'è modo, infatti, di accedere alle variabili interne, se non attraverso le funzioni interne ( i metodi ), mentre la variabile name della funzione esterna è accessibile alle funzioni interne ( Funzioni Javascript ). Naturalmente, se invochiamo i metodi getSex() o getName() una volta caricata la pagina web, come facciamo ora premendo i bottoni equivalenti, ci verranno restituiti solo i valori finali.
Una funzione ricorsiva è una funzione che chiama se stessa. Ne abbiamo già vista una nell'esempio del calcolo fattoriale. Per ogni funzione invocata, il sistema alloca una parte della memoria, in cui impilare i dati della funzione. Ecco perchè questa parte di memoria viene chiamata stack ( pila ). Lo stack dedicato ad una nuova funzione ha inizio nella prima cella libera immediatamente sopra all'ultima già allocata. La zona di memoria che viene creata per una singola funzione viene detta record di attivazione. Il funzionamento dello stack è ben descritto alla pagina: Ricorsione. Il seguente esempio dimostra il funzionamento dello stack:
In ogni record di attivazione viene memorizzato un valore della variabile i, in attesa di eseguire l'ultima istruzione, immediatamente successiva alla chiamata ricorsiva:
per ciascuno degli stack che, via via, verranno deallocati. Nel nostro caso, il primo valore assegnato a i è 3. Quindi, il record di attivazione di questa prima chiamata alla funzione foo() conterrà:
con il quale la funzione eseguirà l'istruzione:
ma non potrà eseguire l'istruzione:
perchè subito dopo la prima istruzione, la funzione invoca se stessa, assegnando alla nuova funzione foo() il valore 2:
L'ultima istruzione:
verrà eseguita solo quando verrà lanciata una nuova funzione foo() con:
condizione grazie alla quale l'esecuzione della funzione viene chiusa:
A questo punto, dovranno essere chiuse tutte le funzioni ricorsive lanciate nel corso del programma, eseguendo quell'ultima istruzione:
per tutti i valori di i trovati in ciascuna delle funzioni ricorsive:
Nel nostro esempio, se chiamate la funzione foo() premendo il bottone foo(3), vi verrà restituito un ulteriore valore, UNDEF, per l'esecuzione dell'istruzione all'interno dello stack della funzione principale, che è rappresentata dal gestore onClick nascosto nel bottone foo(3), che occupa la parte più bassa dello stack. Per verificare la funzione foo(3) senza utilizzare il bottone HTML, attivate l'ultima riga di codice Javascript:
consapevoli del fatto che, in questo caso, la funzione foo() verrà invocata al caricamento della pagina. In questo caso, il valore UNDEF finale non verrà più prodotto.
|
|||||
Le funzioni Javascript | The .bit guides: original contents |