mercoledì 10 ottobre 2012

Hoisting - Sollevamento

In questo post parliamo di JavaScript. Di recente mi sono riavvicinato a questo linguaggio, dopo molto tempo in cui ho cercato di tenermene alla larga, per ovvie ragioni (incompatibilità, scarsa tipizzazione, ecc.). Tuttavia lavorando sulla tesi di laurea ho cominciato ad apprezzare questo linguaggio e a capire quanto può essere potente se utilizzato nella maniera giusta.

Pertanto, in questa serie di post su JavaScript, cercherò di analizzare quelle piccole sfumature che rendono poco intuitivo questo linguaggio, scoraggiandone spesso l'uso ai meno intraprendenti (come è stato nel mio caso). La padronanza di queste tecniche distingue sicuramente un programmatore JavaScript esperto.

NOTA: la trattazione di questi argomenti presuppone una conoscenza, almeno basilare, di programmazione JavaScript.

Partiamo quindi parlando di "hoisting". Il suo significato letterale è "sollevato", il che non ci da una chiara indicazione di cosa effettivamente si tratta.

Lasciamo per un attimo da parte il significato di hoisting e concentriamoci sul codice, in quanto ci sono alcune piccole premesse da fare.

Immagino che voi tutti sappiate la differenza tra dichiarazione ed inizializzazione di variabile. Tuttavia per qualcuno può non sembrare ovvia. Assumendo myValue come variabile possiamo scrivere

Dichiarazione:
var myValue;

Inizializzazione:
var myValue = 1;

Nel secondo caso la variabile è inizializzata con un valore. Potete verificare semplicemente con un istruzione

alert(typeof myValue);

Un altro aspetto a cui occorre prestare attenzione è lo scope. Per chi proviene dai linguaggi della famiglia del C (C,C++,Java,Pascal...) non avrà problemi ad interpretare il seguente programma:

#include <stdio.h>
int main() {
  int x = 1;
  printf("%d, ", x); // output: 1
  if(1) {
    int x = 2;
    printf("%d ", x); // output: 2
  }
  printf("%d\n", x); // output: 1
}

L'output del programma sarà 1, 2, 1. I linguaggi della famiglia del C possiedono visibilità a livello di blocco (block-level scope). Pertanto quando il programma entra nel costrutto if, una nuova variabile x può essere dichiarata senza sovrascrivere la visibilità al di fuori del blocco. Con JavaScript invece il funzionamento è diverso. Proviamo a tradurre il codice precedente in JavaScript:

var x = 1;
alert(x); // output: 1
if(true) {
  var x = 2;
  alert(x); // output: 2
}
alert(x); // output: 2

Sorprendentemente l'output sarà 1, 2, 2. Perché? Semplicemente JavaScript, a differenza dei linguaggi C-like, ha visibilità a livello di funzione. I blocchi come if o while non creano un nuovo scope.

Consideriamo ora il seguente codice:

var myValue = 1;
alert(myValue); // output: 1

Come è prevedibile l'alert mostrerà 1 come output. Proviamo leggermente a modificare questo codice introducendo una funzione immediata (eseguita esattamente dove è dichiarata):

var myValue = 1;
(function() {
  alert(myValue); // output: 1
}());

L'output sarà nuovamente 1. Ovvio direte voi. Bene introduciamo un ulteriore complessità:

var myValue = 1;
(function() {
  alert(myValue); // output: ?
  var myValue = 2;
}());

Qual'è l'output in questo caso? Provatelo in firebug o in jsFiddle, vedrete che il valore dell'alert è undefined. Perché? Intuitivamente il valore dovrebbe essere 1, in quanto la nuova inizializzazione di myValue è al di sotto dell'istruzione alert.

E' qui che entra in gioco l'hoisting. Le dichiarazioni di variabili e funzioni sono sempre spostate ("sollevate"), in maniera invisibile dall'interprete JavaScript, in cima allo scope che le contiene. Pertanto il codice precedente è perfettamente analogo al seguente:

var myValue = 1;
(function() {
  var myValue;
  alert(myValue); // output: undefined
  myValue = 2;
}());

Cominciate a capire meglio come funziona, non è vero?

Fino ad ora abbiamo esaminato l'hoisting applicato alle variabili. Passiamo ora ad esaminare il caso delle funzioni:

function prova() {
  foo(); 
  function foo() {
    alert("funziona!");
  }
}
prova();

Come si comporta questo programma? Anche in questo caso l'hoisting si applica nuovamente. L'interprete JavaScript sposta la dichiarazione della funzione in cima. L'output ovviamente è "funziona", come è lecito aspettarsi. Tuttavia sappiamo che esiste un altro modo di dichiarare le funzioni, assegnandole a variabili (espressione function):

function prova() {
  foo(); 
  var foo = function() {
    alert("non funziona!");
  }
}
prova();

In questo caso riceveremo un errore (Uncaught TypeError: undefined is not a function) dovuto al fatto che solamente il nome della funzione (foo) viene sollevato, mentre il corpo della stessa viene assegnato solamente durante l'esecuzione. Quindi il codice precedente risulta perfettamente analogo a questo:

function prova() {
  var foo;
  foo(); 
  foo = function() {
    alert("non funziona!");
  }
}
prova();


L'analisi può chiudersi qui, nonostante ci siano casi particolari in cui il discorso si fa decisamente più complesso. La lezione più importante che possiamo trarre è di dichiarare le nostre variabili con un unica istruzione var limitata allo scope, sistemandola esattamente in cima, in modo da evitare la confusione derivante dall'hoisting:

function test() {
  var x,y,z;
  // codice
  x = 1;
  // più codice
  y = 2;
  // altro codice
  z = 3;
  // ulteriore codice
  return;
}

Per chi ha famililarità con l'inglese e volesse mettersi alla prova con l'hoisting, consiglio il quiz disponibile all'indirizzo http://www.nczonline.net/blog/2010/01/26/answering-baranovskiys-javascript-quiz/

Alla prossima!

Nessun commento:

Posta un commento