JavaScript: The World’s Most Misunderstood Programming Language
– Douglas Crockford

L’incredibile elasticità del linguaggio JavaScript, lo rende allo stesso tempo uno dei linguaggi più interessanti da studiare e con cui lavorare ma anche uno dei più frustranti (spesso, va detto, senza averne direttamente colpa). La sfortuna del linguaggio, se vogliamo chiamarla così, è dovuta principalmente al fatto che per forza di cose esso ha dovuto legare il suo destino a ciò che Douglas Crockford definisce “la peggiore API mai pensata”, ovvero le API per la manipolazione del DOM. Il linguaggio di per sé, pur avendo delle magagne non da poco, rimane comunque bellissimo ed estremamente potente. Come poi dice sempre Crockford, “non ha più subito errori di design dal 1999!”.

Questa dualità è lampante anche in quello che le librerie/framework che sono state sviluppate negli anni hanno dato al linguaggio stesso: da una parte un sistema più simpatico ed effettivo per lavorare con il DOM, dall’altro hanno aggiunto troppo zucchero sintattico creando così sviluppatori che sanno benissimo come ottenere degli effetti straordinari sulle loro pagine web, ma che avendo a che fare con qualcosa di più complesso o articolato scrivono del JavaScript, beh, migliorabile.

Queste librerie hanno inventato, insomma, dei DSL che poco hanno a che fare con il linguaggio stesso.

In una sorta di back to basic avrei piacere di condividere con chi ne fosse interessato alcune tecniche pratiche di “buon utilizzo” del linguaggio. Trovo infatti poco bello che, mentre da una parte le suddette librerie usino il linguaggio ai massimi livelli, chi usa queste librerie spesso faccia esattamente il contrario (magari poi incolpando il JavaScript stesso di chissà quali crimini “non è sicuro”, “è lento”, “non è accessibile”, eccetera).

Cominciamo con qualcosa tipo “forse non tutti sanno che…” per arrivare poi a qualcosa di ben più corposo…

L’elemento SCRIPT

<script>alert("Hello");</script>

Questo script funziona in tutti i browser moderni, senza la necessità di dover specificare l’attributo type e questo perché per tutti i browser il default è, appunto, “text/javascript”. Il type è però un’attributo obbligatorio in HTML e dunque va sempre messo. Se però hai bisogno di fare una prova veloce, beh, puoi ometterlo e risparmiare qualche battuta.

Il punto e virgola

È risaputo che in JavaScript non è obbligatorio aggiungere il punto e virgola in fondo ad ogni statement; in realtà le cose stanno un po’ diversamente: se non metti il punto e virgola, JavaScript lo metterà implicitamente al posto tuo (semicolon insertion).
Se da una parte questo può essere considerato comodo dall’altra è considerato proprio un errore di design nelle specifiche JavaScript. I problemi che questo comportamento può generare sono diversi:

  • tutte le volte che il JavaScript incontra un errore di parsing, tenterà di aggiungere un punto e virgola nello statement precedente (se non già presente) e rieffettuerà il parsing
  • utilizzare un sistema di minimizzazione del proprio codice dove non si sia usato sempre il punto virgola, potrebbe portare a conseguenze inattese (se non proprio ad errori di sintassi)

Esiste anche un altro problema molto più subdolo generato dal “semicolon insertion”, ma per la cui analisi vi rimando a questo articoletto.

Per cui usiamo sempre il punto e virgola e vivremo felici.

Variabili globali

Tutte le volte che si definisce una funzione, un oggetto o una variabile fuori da un oggetto definito da noi stessi, questo diventa proprietà dell’oggetto globale che, semplificando, nei browser è l’oggettone “window”. Tale oggetto è sempre presente nei nostri documenti e per questo è stato deciso di rendere implicita la sua (de)referenziazione.

Per accedere dunque alle sue proprietà non è necessario specificare “window.” prima di esse. Questo comportamento implicito, genera uno dei più comuni errori da parte degli sviluppatori, ovvero quello di “sporcare” inconsapevolmente il namespace globale. A meno che non lo si faccia intenzionalmente e con tutte le cautele, inserire oggetti e variabili all’interno del namespace globale è da considerarsi la più famosa delle bad practice. I motivi sono diversi, primo fra questi il fatto che tale spazio di memoria è condiviso fra TUTTI gli script che girano all’interno della pagina. Non è dato sapere, al momento di definire una variabile globale, se questa entrerà o meno in “collisione” con un’altra.

(nota: si può incontrare talvolta “self” come alias di “window”)

Ecco un esempio

<script type="text/javascript">
function foobar() {
  zoo = 10;
}
foobar();
alert(zoo);
alert(window.zoo);
</script>

Sorpresa! La variabile zoo, pur essendo stata inizializzata all’interno di una funzione non viene allocata come simbolo locale della stessa, ma come simbolo globale, automaticamente ereditato da… window. Per questo i due alert riporteranno il valore “10″. Il motivo è un po’ complesso da spiegare, ma come vedremo meglio più avanti ha a che fare con il “contesto” nel quale tale variabile viene usata. Se non diversamente specificato, JavaScript risale l’albero delle dipendenze dell’oggetto che si sta usando (zoo, in questo caso), per cercarne la sua definizione. Non trovando la definizione all’interno della funzione “foo”, è costretto a salire al livello superiore (window) e aggiungere tale simbolo a quell’oggetto (che è l’ultima cosa che gli rimane da fare).

Il comportamento corretto si ottiene definendo sempre le variabili con l’apposita parola chiave var. In questo caso tale variabile rimarrà chiusa nel contesto della funziona foobar() e non “risalirà” verso window.

function foobar() {
  var zoo = 10;
}
alert(zoo); // Errore (giustamente...)

La variabile THIS

Come è risaputo, ogni volta che ci troviamo all’interno di un oggetto, possiamo usare la variabile speciale this per aver un riferimento all’oggetto stesso, in modo da accedere in lettura o scrittura alle proprietà e metodi dell’istanza.

Ma in javaScript siamo sempre all’interno di un oggetto, per cui abbiamo sempre a disposizione un “this”. Il problema nasce, dunque, quando cerchiamo di capire, nel contesto dove ci troviamo in un determinato momento, che diavolo sia il “this” che abbiamo tra le mani. Non avere chiaro il funzionamento di questo elemento è fonte di tantissimi errori o misteriosi malfunzionamenti.

Cerchiamo di fare chiarezza.

<script type="text/javascript">
alert(this);
</script>

Come credo ci si potesse aspettare, ci verrà detto che quell’oggetto non è altro che l’istanza di “window”, l’oggettone padre.

Proviamo così, allora:

<script type="text/javascript">
function foobar() {
  alert(this);
}
foobar();
</script>

Uhm, ci verrà ancora detto “window”. Anche qui siamo abbastanza d’accordo, perché vale il discorso sul contesto fatto poco fa: JavaScript si accorge che il contesto non è quello di un oggetto custom, per cui non riuscendo a referenziare il “this” come simbolo locale, torna indietro nell’albero fino a trovare window, il quale gli dirà “sì, quel this sono io”.

Adesso usiamo foobar come costruttore di oggetti (la chiave è tutta nel “usiamo come…”).

<script type="text/javascript">
function foobar() {
  alert(this);
}
var zoo = new foobar();
</script>

Ecco, usando foobar come costruttore JavaScript “capirà” che siamo all’interno di un oggetto e il nostro this sarà l’oggetto stesso (il this, implicitamente, sarà ritornato dalla funzione costruttore stessa ed assegnato alla variabile zoo).

Purtroppo, però, una cosa di questo genere è perfettamente legale:

<script type="text/javascript">
function foobar() {
  alert(this);
}
var zoo = new foobar();
foobar();
</script>

Nel primo caso la funzione è usata come costruttore, nel secondo caso no. Nei due casi, dunque, il “this” sarà qualcosa di diverso.

Crockford, dato questo comportamento, ha deciso e consiglia di non usare mai la keyword “new”, ma usare invece un pattern che permetta di creare funzioni che generano sempre nuovi oggetti… ma questo è un altro discorso.

Il module pattern

Per concludere questa carrellata, voglio descrivere questo pattern pensato e reclamizzato sempre da Crockford. Il module pattern è un sistema che permette di creare singleton che servono per avere sempre un namespace all’interno del quale far “vivere” il nostro codice ed essere sicuri di non aver mai a che fare con il namespace globale. Permette anche di avere dei metodi e variabili “private” all’interno del nostro singleton.

Vediamo l’esempio più semplice possibile e scopriamo il “trucco” dietro questo pattern:

<script type="text/javascript">
var myNamespace = (function() {
  return {
    zoo: 5;
  }
})();  // <-- notare le parentesi!
alert(myNamespace.zoo);
</script>

Il trucco fa uso delle funzioni anonime del JavaScript; infatti myNamespace non viene inizializzato con la function(), ma con il return della stessa: la funzione, infatti, è effettivamente chiamata – ecco il motivo della presenza della coppia “()” alla fine della definizione – per restituire un oggetto literal.

Non vi fate ingannare dalla coppia di parentesi tonde che racchiude il corpo della funzione: è soltanto una convenzione adottata per poter notare a colpo d’occhio che ci stiamo trovando di fronte a questo pattern; il sistema funziona anche senza di esse.

Metodi e variabili private, dunque? Nessun problema:

var myNamespace = (function() {
  var pProperty=42;
  function pMethod() {
    pProperty = 33;
  }
  return {
    bee: function() {
      pMethod();
      return pProperty;
    }
  }
})();  // <-- notare le parentesi!
alert(myNamespace.bee());

“pProperty” e “pMethod” sono definiti nel contesto locale della funzione che ritorna il nostro singleton; questo vuol dire che non sono visibili all’esterno, ma solo dal suo interno e, dunque, dall’oggetto ritornato.

Notare che qui si fa uso dell’altro concetto “killer” del JavaScript, ovvero delle closure, per il quale vi rimando ad un altro mio articolo, ben più completo, sull’argomento. In pratica è questo il motivo per cui le variabili locali alla funzione continuano a “vivere” anche dopo che la funzione è terminata: sono state chiuse, intrappolate e rese disponibili all’oggetto ritornato finché quello sarà utilizzato.

E il nostro amico this, in tutto questo? Nessun problema? Mmmmh…

Immaginiamo, per fare un esempio di un problema ricorrente, che il nostro singleton abbia anche a che fare con il DOM e gli eventi; qualcosa del genere:

<input type="button" id="button" value="Click me">

...

var myNamespace = (function() {
  return {
    init: function() {
      var el=document.getElementById('button');
      el.addEventListener('click', function() {
        alert(this);
      }, true)

    }
  }
})();
myNamespace.init();

Cosa succederà quando premerò quel bottone? A cosa corrisponderà quel “this”? Siamo all’interno di un oggetto, dunque probabilmente avrò un riferimento all’oggetto stesso, no?

No. Manco per niente.

Il fatto è che la funzione di callback sull’evento “click” del bottone viene eseguita dal browser assolutamente fuori dal contesto del nostro oggetto; questo vuol dire che il “this” sarà il “this” di tutte le funzioni callback (ovvero, in questo caso, sarà un riferimento all’oggetto DOM relativo al pulsante stesso); niente a che vedere con il nostro potentissimo singleton.

Il problema è: quando premo il pulsante voglio eseguire un metodo del singleton; ecco come fare, closures to the rescue!:

var myNamespace = (function() {
  var that;
  return {
    init: function() {
      that=this;
      var el=document.getElementById('button');
      el.addEventListener('click', function() {
        that.foobar(this);
      }, true);
    },

    foobar: function(el) {
      alert("You clicked on " + el.id);
    }
  }
})();

myNamespace.init();

Il trucchetto sta ancora una volta nell’usare il concetto di closure. Quando eseguo la funzione di init, non devo fare altro che “salvare” il this all’interno di una variabile locale che, spiritosamente, ho chiamato that (non c’è nessun motivo per usare proprio quel nome, è soltanto una piccola convenzione).

Adesso anche la funzione di callback è stata “intrappolata” nello scope del nostro oggetto che, appunto, ha accesso alle variabili locali della funzione all’interno della quale è stata definita. Esso dunque è “costretto”, volente o nolente, a darci accesso al that che corrisponde al nostro singleton.

Termino qui, sperando che siano più le cose chiarite che quelle che sono riuscito a confondere :)

Print