LE FUNZIONI - PARTE 2ª

Funzioni che chiamano altre funzioni

Argomenti trattati: richiamare altre funzioni nel codice JavaScript, funzioni ricorsive.

Finora abbiamo visto come chiamare una funzione da una parte qualsiasi di una pagina HTML delimitata dai tag <script> e </script>. Nulla vieta - e, anzi, è un'operazione piuttosto frequente - di chiamare una funzione dall'interno di un'altra funzione. Si parla allora in questi casi di funzione chiamante e di funzione chiamata. Questo rende possibile che sia direttamente una funzione a passare dei parametri a un'altra funzione. A parte il diverso àmbito in cui una funzione viene chiamata, non cambia altro. Si osservi il listato d'esempio:

<html>
<title>Funzione chiamante</title>
<head>
<script language="JavaScript">
<!--
function f1()  
  {
     f2('argomento di prova');
  }

function f2(arg1)
  {
     alert('Questo è l\'argomento ricevuto da f1(): ' + arg1);
  }
-->
</script>
</head>
<body>
<script language="JavaScript">
<!--
f1();  
-->
</script>
</body>
</html>

// Nel codice d'esempio una funzione non parametrizzata chiamata f1() invocata nell'area compresa tra i tag <body> e </body> chiama a sua volta, passandole un argomento, la funzione parametrizzata f2(). Questa seconda funzione visualizzerà l'argomento con cui è stata chiamata da f1().

Ovviamente non c'è limite alle funzioni che possono essere chiamate all'interno di una funzione e questo dovrebbe dare l'idea delle possibilità quasi infinite che si possono ottenere mediante il loro utilizzo.

Funzioni che restituiscono un valore

Finora abbiamo visto come utilizzare le funzioni semplicemente come delle porzioni di codice da richiamare grazie ad un identificatore alfanumerico allo scopo di eseguire delle qualsiasi istruzioni. Abbiamo anche visto, nel caso di funzioni parametrizzate, come queste non possano modificare la versione originale del proprio argomento ma, eventualmente, solo la copia in locale che viene creata quando la funzione è chiamata, e distrutta quando essa termina. Tuttavia, esiste un modo in JavaScript per ottenere ugualmente il risultato di un'operazione eseguita sugli argomenti di una funzione quando questa ha termine. Questa possibilità è comunemente definita come restituzione di un valore. La "restituzione" è nei confronti della funzione chiamante o comunque di quella porzione di codice che ha invocato la funzione stessa, che, come abbiamo visto, può anche essere un semplice frammento di codice delimitato dai tag <script> e </script> dell'HTML e non apppartenente ad alcuna funzione. La parola chiave che permette ad una funzione di restituire un valore è return a cui viene fatto seguire il valore da restituire, che può essere il nome di una variabile o un dato di uno qualsiasi dei sottotipi fondamentali di JavaScript. L'esempio che segue mostra una funzione che restituisce un valore alla funzione chiamante.

<html>
<title>Restituzione di un valore</title>
<head>
<script language="JavaScript">
<!--
function chiama()  
  {
     var num=1, res;
     res=restituisci(num);
     alert('Il valore restituito è: ' + res);
  }

function restituisci(arg1)
  {
     alert('Il valore ricevuto da \'chiama\' è: ' + arg1);
     arg1++;
     return arg1;
  }
-->
</script>
</head>
<body>
<script language="JavaScript">
<!--
chiama();  
-->
</script>
</body>
</html>

// Il listato d'esempio mostra una funziona di nome chiama() che invoca la funzione restituisci() passandole un parametro numerico come argomento per mezzo di una variabile chiamata num; la funzione restituisci(), dopo aver visualizzato l'argomento ricevuto da chiama(), va a modificare il contenuto della copia in locale di num incrementandola di una unità, restituendo alla funzione chiamante, per mezzo della parola chiave return, il valore dell'argomento cosí modificato. Da notare l'assegnamento che avviene all'interno della funzione chiama(): a una variabile di nome res viene assegnato il valore che sarà restituito dalla funzione restituisci. Sarà per mezzo di quella variabile che poi tale valore verrà visualizzato dalla funzione chiamante. Dovrebbe essere chiaro che, anche in questo caso, l'originale della variabile num non subisce alcuna modifica, rimanendo uguale a 1.

Nonostante quella di assegnare a una variabile il risultato restituito da una funzione sia una pratica molto frequente, è anche possibile utilizzare tale valore direttamente, senza l'"intermediazione", per cosí dire, di una variabile. In JavaScript, come in tutti i linguaggi, una funzione o un'istruzione nativa del linguaggio può avere come argomento un'altra funzione. Di conseguenza, per fare un esempio, un'istruzione alert può visualizzare direttamente il valore restituito da una funzione "ospitando" tra le parentesi tonde il nome della funzione stessa, anziché quello di una variabile a cui ne è stato assegnato il valore. Per maggiore chiarezza osservare l'esempio qui sotto, che è identico al precedente, eccettuata la mancanza della variabile res.

<html>
<title>Restituzione di un valore</title>
<head>
<script language="JavaScript">
<!--
function chiama()  
  {
     var num=1;
     alert('Il valore restituito è: ' + restituisci(num));
  }

function restituisci(arg1)
  {
     alert('Il valore ricevuto da \'chiama\' è: ' + arg1);
     arg1++;
     return arg1;
  }
-->
</script>
</head>
<body>
<script language="JavaScript">
<!--
chiama();  
-->
</script>
</body>
</html>

Funzioni ricorsive

Per funzione ricorsiva si intende una funzione che richiama sé stessa. Dal punto di vista teorico, un problema, o, per usare un termine piú propriamente informatico, un algoritmo, che può essere risolto in maniera ricorsiva può anche essere risolto in maniera iterativa, ossia ricorrendo all'utilizzo dei
cicli. La differenza, in termini di prestazioni, è a leggero favore di questi ultimi, in quanto, la chiamata costante di una funzione in tempi brevi può sovraccaricare la memoria, tanto piú nel caso di un linguaggio interpretato come JavaScript. Tuttavia, in alcuni casi, le funzioni ricorsive conferiscono una leggibilità e un'"eleganza" al codice non raggiungibili altrimenti. In generale, comunque, si può dire che i due metodi si equivalgono e che spetterà alle preferenze individuali il compito di far prevalere l'uno o l'altro. Vediamo però un esempio di un classico algoritmo in cui viene usata la ricorsione: quello del calcolo del fattoriale di un numero. Per chi avesse poca familiarità con la matematica - come chi scrive :) - si ricorda che il fattoriale di un numero N si ottiene moltiplicando tra loro tutti i numeri compresi tra N e 1. Per esempio, il fattoriale di 6, che viene scritto !6, è uguale a: 6x5x4x3x2x1. Si è usato il simbolo x per indicare la moltiplicazione perché quella mostrata è una formula matematica. La sua "trasposizione" in codice JavaScript è mostrata nel listato qui sotto:

<html>
<title>Esempio di funzione ricorsiva: fattoriale di un numero</title>
<head>
<script language="JavaScript">
<!--
function fattoriale(N)  
  {
     var risultato;
     if (N > 1) {
        risultato = N * fattoriale(N - 1);
     }
     else {
        risultato = 1;
     }
     return risultato;
  }
-->
</script>
</head>
<body>
<script language="JavaScript">
<!--
alert('Il fattoriale di 6 è: ' + fattoriale(6));
-->
</script>
</body>
</html>

// Nell'esempio appena visto, una funzione di nome fattoriale() che accetta un argomento sulla riga di comando richiama sé stessa all'interno del suo codice con l'argomento di volta in volta decrementato di uno fino a che tale argomento sarà maggiore di uno. Il numero ricevuto viene moltiplicato per quello immediatamente precedente: N - 1. Quando il numero ricevuto sarà uguale a uno, la variabile risultato sarà impostata ad uno e tutte le chiamate alla funzione cominceranno a ritornare in ordine inverso: dall'ultima alla prima. Infatti, finché il numero ricevuto come argomento sarà maggiore di uno, la funzione non eseguirà altra operazione se non continuare a chiamare sé stessa; arrivata alla terza riga risultato=N*fattoriale(N-1);, la "lettura" del codice da parte dell'interprete si bloccherà per ricominciare da capo. Solo quando la condizione iniziale sarà soddisfatta l'esecuzione potrà passare al ramo dell'else, permettendo cosí a tutte le altre chiamate accodate in attesa di terminare la loro esecuzione.

Dal momento che la comprensione della ricorsività delle funzioni può procurare qualche difficoltà, viene ora mostrato un altro esempio, che dovrebbe aiutare a comprendere come in effetti le chiamate a una funzione ricorsiva ritornino in ordine inverso. L'esempio seguente eseguirà un conto alla rovescia da 10 a 0 anche se nel codice, in realtà, i numeri verranno passati in ordine crescente.

<html>
<title>Esempio di funzione ricorsiva: conto alla rovescia</title>
<head>
<script language="JavaScript">
<!--
function rovescia(N)  
  {
     if (N <= 10) {
        rovescia(N + 1);
     }
     else {
        return;
     }
     alert(N);
  }
-->
</script>
</head>
<body>
<script language="JavaScript">
<!--
rovescia(0);
-->
</script>
</body>
</html>

// Come si può facilmente notare, questo secondo esempio è leggermente diverso dal primo. Innanzitutto la funzione rovescia(), al contrario della funzione per il calcolo del fattoriale di un numero, non restituisce alcun valore, visualizzando essa stessa l'output con un'istruzione alert. La parola chiave return usata isolatamente serve solo per terminare la serie di chiamate alla funzione stessa. Fino a che il numero ricevuto come argomento è inferiore o uguale a 10 rovescia() richiama sé stessa; quando la condizione viene soddisfatta la serie di chiamate ricorsive viene interrotta e, dalla prima all'ultima, tali chiamate terminano la propria esecuzione mostrando ciascuna il numero ricevuto come argomento. Ecco perché, pur passando come argomento ogni volta un numero incrementato di una unità - rovescia(N+1); - il conto viene visualizzato in ordine inverso. Da questo consegue che, se avessimo voluto contare in ordine crescente, avremmo dovuto fare l'esatto opposto; cioè impostare la condizione come if (N>=0) rovescia(N-1);.

Passaggio per riferimento

Abbiamo visto nella prima parte sulle funzioni come il passaggio di argomenti avvenga per valore quando gli argomenti passati sono costituiti da semplici variabili. Esiste anche un passaggio detto per riferimento in cui, a differenza di quanto avviene nel primo caso, le eventuali modifiche operate da una funzione sui propri argomenti non avvengono su una copia in locale ma direttamente sull'originale. Questo tipo di passaggio, però, avviene esclusivamente quando a una funzione vengono passati degli oggetti per cui si rimanda alla trattazione di questi ultimi per ulteriori approfondimenti. Per il momento basti sapere che il passaggio di oggetti a una funzione avviene sempre per riferimento.

Riusabilità del codice

Nella prima parte sulle funzioni si è anche accennato alla possibilità di riutilizzare il codice grazie alle funzioni. Piú o meno tutti i linguaggi moderni offrono questa possibilità, ognuno in modi leggermente diversi. In JavaScript ciò avviene grazie all'utilizzo di un particolare attributo del tag <script> dell'HTML. L'attributo in questione è src e sta per source, sorgente, origine. Grazie a esso è possibile includere una o piú funzioni JavaScript in un unico file di testo con estensione .js. Si ha cosí la possibilità di utilizzarlo in un numero illimitato di pagine HTML. Tale possibilità è utilissima per gli sviluppatori perché consente di scrivere una funzione una sola volta e di poterla usare senza limitazioni. Inoltre, qualora vi sia la necessità di apportare delle modifiche al codice, sarà sufficiente modificare il solo file sorgente anziché andare a modificare ogni singola pagina HTML. L'esempio seguente mostra l'inclusione di un file .js che contiene una funzione che viene richiamata nella stessa pagina.

<html>
<title>Esempio di inclusione di un file .js</title>
<head>
<script src="libreria.js">
</script>
</head>
<body>
<script language="JavaScript">
<!--
mia_Funzione();
-->
</script>
</body>
</html>

// Si può notare come, a parte l'intestazione dell'attributo src del tag <script>, nulla cambi per l'utilizzo di una funzione. Essa andrà scritta nel file sorgente nello stesso identico modo in cui si sarebbe scritta nella pagina che ne fa uso. Successivamente quando la si andrà a richiamare, la funzione verrà caricata dal file sorgente anziché dalla pagina stessa.

Conclusioni

Con la trattazione delle funzioni si conclude la parte "tradizionale" della sintassi di JavaScript; quella, cioè, che prescinde dalle caratteristiche di un moderno linguaggio basato sugli oggetti. Proprio questi saranno il prossimo argomento di questo corso. Si raccomanda, prima di affrontarne la trattazione, di rivedere con attenzione quanto visto finora, soprattutto se è la prima volta che si intraprende lo studio di un linguaggio di programmazione. È importante avere ben chiari tutti gli argomenti svolti finora prima di affrontare una materia importante e impegnativa, per i concetti nuovi che porta con sé, come lo studio degli oggetti JavaScript.


Torna all'inizio | Home