Pillole di Javascript: variabili

Lo scope di una variabile è la parte del programma nella quale la variabile risulta definita. In molti linguaggi, ad esempio in Java, lo scope è a livello di blocco, dove il blocco solitamente è delimitato dalle parentesi graffe. In Javascript lo scope può essere a livello di funzione oppure globale. Se una variabile è dichiarata all’interno di una funzione, lo scope sarà delimitato dalla funzione stessa, se è dichiarata al di fuori di qualsiasi funzione, avrà scope globale.

La sintassi per la dichiarazione di una variabile prevede l’utilizzo della keyword var, seguita dal nome della variabile ed, opzionalmente, la sua inizializzazione.

var mondo = "world"; // variabile globale

function func() {
 var saluto = "hello"; // variabile locale alla funzione
 console.log(saluto + " " + mondo); // => "hello world"
}

func(); // invoco la funzione appena definita

console.log(saluto + " " + mondo); //ReferenceError

Lo scope globale è implementato tramite un oggetto chiamato oggetto globale, che viene creato dall’interprete all’avvio. Una variabile globale è una property di questo oggetto. Nell’oggetto globale ci sono anche molte property che sono create e inizializzate di default dall’interprete. Ad esempio i costruttori Number, String, Boolean e RegExp, l’oggetto Math, la property “undefined” e molte altre.

Anche lo scope di una funzione può essere visto come un oggetto, le cui property sono le variabili definite nella funzione, compresi i parametri della funzione. Tuttavia questo è più che altro un dettaglio implementativo, ed è trasparente per lo sviluppatore.

Se un’istruzione, che si trova fuori da ogni funzione, referenzia una variabile, l’interprete risolverà il nome della variabile cercandola nell’oggetto globale. Se invece il riferimento viene fatto all’interno di una funzione, la ricerca avverà prima nello scope della funzione e, se questo non contiene la variabile referenziata, allora verrà cercata nello scope globale.

In caso di lettura della variabile, se questa non è presente neanche nello scope globale, viene sollevato un ReferenceError. Se invece si tratta di un’assegnazione, allora viene creata una variabile globale, ovvero una property dell’oggetto globale.

function test() {
 var n1 = 3; // dichiarata variabile locale
 n1 = 4; // assegnazione a variabile locale
 n2 = 42; // nuova variabile globale
}

Se lo scope della funzione definisce una variabile che ha lo stesso nome di una variabile globale, la variabile nella funzione coprirà il nome di quella globale. Questo comportamento può essere causa di errori, soprattutto se si considera l’hoisting (spiegato più avanti), ed è uno dei motivi per cui l’utilizzo delle variabili globali è considerato una cattiva pratica.

La dichiarazione di una variabile può trovarsi in qualsiasi punto della funzione. Tuttavia le variabili vengono create già da prima della loro dichiarazione. Ad esempio nel seguente listato:

var value = "global";

function test() {
 console.log(value);
 var value = "local";
}

intuitivamente l’output dovrebbe essere “global”, e invece eseguendo il codice l’output sarà “undefined”. Questo succede perché prima di eseguire la funzione l’interprete analizza il codice alla ricerca di tutte le variabili definite, crea le variabili trovate, e infine inizia l’esecuzione. Quando viene stampato il log, la variabile globale “value” è già stata coperta dalla variabile locale con lo stesso nome, nonostante la definizione di questa avvenga solo nell’istruzione successiva. Questo comportamento viene detto “hoisting delle variabili“, ed è usato sia a livello di funzione che a livello di script.

Le funzioni possono essere annidate tra di loro. Per risolvere il nome di una variabile l’interprete cercherà prima nello scope della funzione più interna, poi in quello della funzione precendente, e così via, fino ad arrivare allo scope globale. Gli scope formeranno quindi una catena, detta scope-chain. La scope chain viene percorsa dalla fine (la funzione più interna) verso l’inizio (l’oggetto globale) alla ricerca della variabile referenziata.

global object <- function 1 <- function 2 <- ….. <- function n

var mondo = "world"; // variabile globale

function outer() {
  var saluto = "hello"; // variabile locale alla funzione outer
  function inner() {
    // inner può accedere sia allo scope di outer che a quello globale
    console.log(saluto + " " + mondo);
  }
}
Precedente Pillole di Javascript: gli operatori