martedì 16 ottobre 2012

Closures


In questo nuovo appuntamento parlerò di closures. Come per quanto riguarda l'hoisting, le closures sono un concetto fondamentale per comprendere appieno la potenza di JavaScript, in quanto permettono di creare funzioni personalizzate che vincolano le variabili in un ambito. Sono usate generalmente per costruire funzioni di callback.

Una buona definizione di closure si può trovare sul sito Mozilla Developers Network, da cui questo articolo prende ispirazione:

"una closure è un oggetto particolare che combina due cose: una funzione e l'ambiente in cui la funzione è stata creata."

Per capire meglio partiamo con un primo esempio:

function init() {
  var myName = "Mauri";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();
la funzione init() crea una variabile locale myName (che possiamo considerare come parte dell'environment o ambiente) e definisce una funzione interna chiamata displayName(). Questa funzione dispone della variabile dichiarata e inizializzata nella funzione esterna. Il funzionamento di init() è intuitivo ed è un semplice esempio di functional scoping, nel quale le funzioni annidate possono accedere alle variabili dichiarate all'esterno.

Ora proviamo a modificare l'esempio come segue:
function makeDisplay() {
  var myName = "Mauri";
  function displayName() {
    alert(name);
  }
  // NB: una funzione è una variabile,quindi può essere ritornata
  return displayName;
}

var display = makeDisplay();
display();
Il comportamento di questo codice è identico a quello precedente, potete provarlo comodamente su jsFiddle. Ciò che distingue questo esempio è il fatto che la funzione interna viene ritornata dalla funzione esterna prima di essere eseguita. Intuitivamente ci si aspetta che, terminata l'esecuzione di makeDisplay(), la variabile interna myName sia cancellata dallo stack (come avviene nei linguaggi della famiglia C) e non possa venire referenziata da display(), tuttavia l'esecuzione del codice dimostra che la variabile è ancora accessibile è può essere utilizzata.

Questo peculiare aspetto è propro ciò che caratterizza le closures. La variabile myFunction "racchiude" al suo interno la funzione displayName() e le eventuali variabili accessibili alla funzione durante la creazione della closure. Un esempio (variazione spudorata di quello su MDN) un pochino più complesso può essere il seguente:
function increment(base) {
    this.base = base;
    return function(value) {
        return base + value;
    }
} 

var addTo5 = increment(5);
var addTo14 = increment(14);

alert(addTo5(5)); // output: 10
alert(addTo14(3)); // output: 17
La funzione increment in questo caso è un esempio di function factory, ovvero una funzione che aggiunge un valore specifico all'argomento passato. Questa function factory è stata utilizzata per creare due nuove funzioni (addTo5 e addTo14) che impostano rispettivamente, nel proprio ambiente, i valori 5 e 14 come base a cui verranno aggiunti gli argomenti passati (5 e 2). addTo5 e addTo14 sono quindi closures.

E' importante ricordarsi che le variabili interne sono passate per riferimento e non per copia:
function dica33() {
  var num = 32;
  var showAlert = function() {
    alert(num);
  }
  num++;
  return showAlert();
}

var show33 = dica33();
show33();
Per oggi mi fermo qui, ma non è detto che in futuro possa portare ulteriori approfondimenti sul tema.

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!