Javascript

Da Skypedia.
Netscape Navigator 9
JavaScript nacque a metà degli anni '90 nei laboratori Netscape come LiveScript. Dalla collaborazione con Sun questo linguaggio di scripting fu annunciato al pubblico come JavaScript per ragioni commerciali e per legarlo al fatto che la sintassi simil-C di Java era in effetti un elemento di analogia con lo sviluppo di questo nuovo e interessante linguaggio che prometteva di poter cambiare il modo di interpretare il nascente web tramite l'esecuzione di programmi client-side direttamente all'interno dei web browser (allora ovviamente Netscape - oggi ormai estinto). JavaScript nacque come linguaggio snello e flessibile che doveva essere di facile comprensione e scrittura e che non doveva richiedere la complessità propria del "fratello maggiore" Java sviluppato dalla Sun Microsystems (oggi ormai divenuta parte di Oracle); purtroppo però ciò portò anche a ingenerare spesso e volentieri un problema di confusione legato ai nomi dei due linguaggi e ancora oggi, a distanza di circa sedici anni, ci sono molti programmatori che non hanno ben chiara la distinzione tra Java e JavaScript.

Se volessimo includere uno script JS (JavaScript) all'interno di una pagina XHTML dovremmo semplicemente inserire un codice del genere:

<script type="text/javascript">
// <![CDATA[
                      JavaScript statements...
// ]]>
</script>

La sintassi sopra esposta indica al parser XHTML di saltare la porzione di Character DATA (CDATA) dal resto della pagina e consente di evitare conflitti. I simboli '//' all'inizio di una linea segnalano l'inizio di un commento JavaScript, per impedire che <![CDATA[ e ]]> vengano analizzati dallo script. In pratica si racchiude in <![CDATA[ ]]> la sintassi JavaScript, mentre il resto sarà visto come parte del documento XHTML.

L'importanza delle funzioni in JavaScript e le callback

Le funzioni in JS sono gli elementi di base del paradigma di programmazione. Questo consente anche di assegnare il nome di una funzione come argomento della chiamata ad un metodo qualsiasi. In tal modo è possibile applicare un modello funziona di programmazione tale per cui le funzioni supportano la struttura a callback.

Functions in Javascript are 'First class citizens' and so can be passed around like variable references and executed at a later time.

Un callback non è altro che una funzione passata come argomento di un'altra funzione ed essa viene eseguita appena la funzione madre finisce la propria esecuzione. Si ha così un meccanismo di concatenazione delle funzioni che può gestire la comparsa di eventi tramite degli handler che implementino un pattern di tipo Observer.

Un esempio di callback (utilizzando il framework jQuery) può essere il seguente:

jQuery.get('myhtmlpage.html', myCallBack);

myCallBack è il nome di un metodo che verrà eseguito dopo il completamento di jQuery.get(). Non viene passato utilizzando le parentesi come myCallBack(), ma viene soltanto passata la firma del metodo stesso come se fosse una semplice variabile. Risulta comunque possibile invocare il metodo di callback con la specifica dei parametri di funzione che saranno effettivamente, solo che questo non può essere eseguito nel modo seguente:

jQuery.get('myhtmlpage.html', myCallBack(param1, param2));

Il metodo get andrebbe in esecuzione solo dopo l'esecuzione di myCallBack(param1, param2), alla conclusione di questa routine il controllo ripasserebbe a get che riceverebbe come secondo parametro il valore di ritorno di myCallBack. Questo non sarebbe affatto un funzionamento a callback.

Per ovviare a tale problema si può utilizzare questo escamotage:

jQuery.get('myhtmlpage.html', function(){
  myCallBack(param1, param2);
});

In pratica il meccanismo di invocazione tramite callback pretende l'utilizzo di puntatori a funzione come parametro di una funzione genitrice, dunque per far sì che ciò avvenga mascheriamo l'invocazione di una funzione con parametri all'interno di una funzione anonima e in tal modo si avrà il puntatore a funzione (provvisto di parametri) come valore di ritorno della funzione anonima. In questo modo abbiamo semplicemente utilizzato la funzione anonima function(){ come segnaposto per la nostra funzione callback.

La programmazione ad oggetti in JavaScript

Per poter utilizzare un linguaggio di programmazione in modo che sia Object Oriented, bisognerebbe poter creare degli oggetti in termini di classi di oggetti. Questa funzionalità dovrebbe essere fornita dallo stesso linguaggio di programmazione e in questo caso è Javascript il linguaggio considerato. Quando parliamo di classe intendiamo una porzione di codice portabile e riutilizzabile corredata di attributi, che rappresentano delle informazioni a corredo del dato oggetto le quali potranno essere impostate o richieste a seconda della loro visibilità. Inoltre le classi saranno in grado di effettuare una o più operazioni attraverso metodi, che non saranno altro che funzioni interne all'oggetto, siano esse pubbliche o private.

La gerarchia degli oggetti fondamentali su cui opera JavaScript

Gli oggetti fondamentali del DOM
Come gli altri linguaggi ad oggetti anche JavaScript ha una linea ereditaria che definisce una gerarchia per la manipolazione delle classi e una classe madre da cui tutte le altre sono costrette ad ereditare. Ovviamente tale suddivisione è legata strettamente a come è stato strutturato il Document Object Model (DOM) e quindi a tutti gli elementi di una finestra del browser e tutti gli elementi principali dell'HTML. JavaScript, è bene chiarirlo una volta per tutte, opera su elementi di una pagina HTML (XHTML) all'interno di una finestra di un browser come Mozilla Firefox; di conseguenza, tutto ciò che può essere "scriptato" è legato a doppio filo con ciò che compone una struttura standard W3C e gli elementi comuni a tutti i tipi di browser.

Differenze con C++

A differenza di molti linguaggi ad oggetti come C++, Java o C#, JavaScript ha solo dei meccanismi basilari per la programmazione OOP. Alcune differenze rispetto al famoso e potente C++:

  • JavaScript non permette l'ereditarietà
  • il JavaScript non permette l'utilizzo di template. E' da precisare che l'utilizzo dei template in JavaScript, almeno per il momento, è impensabile perchè le strutture dati (variabili, vettori...) non sono tipizzate e quindi possono assume qualsiasi tipo di dato.
  • JavaScript non permette la creazione di classi o metodi astratti.
  • JavaScript non è possibile specificare metodi o attributi private, protected, o friend. Tutti i metodi e attributi sono pubblici, anche se si può mascherare lo scope di tali metodi come esposto di seguito.

La prima classe JavaScript

In Java, per creare un nuovo oggetto si utilizza la parola chiave class, in Javascript non vi è una parola chiave di questo tipo, ma si definiscono delle funzioni in modo tale che esse possano operare come oggetti.

function NuovaClasseJS(){};			// funzione NuovaClasseJS
var oggettoJS = new NuovaClasseJS();	        // oggettoJS è una istanza NuovaClasseJS
					        // NuovaClasseJS è istanziata come classe [grazie a new]

alert(oggettoJS instanceof NuovaClasseJS);	// true
alert(NuovaClasseJS instanceof Function);	// true
alert(oggettoJS instanceof Function);	        // false

Come si può osservare nel precedente codice è possibile creare un nuovo oggetto tramite l'operatore new. Se, infatti avessimo richiamato la funzione NuovaClasseJS() questa sarebbe stata semplicemente un riferimento a quella data funzione.

Un elemento interessante di Javascript è la possibilità di assegnare dei riferimenti a funzione e di passare delle variabili che corrispondono a delle vere e proprie funzioni. Partendo da questo presupposto l'espressione

var fnref = function() {};

è ammesso dalla sintassi e questo richiama elementi di programmazione funzionale. Anche il solo identificare una funzione come una sorta di variabile e farla operare inline è simile a quanto avviene con le funzioni lambda. A questo punto per creare un nuovo oggetto possiamo utilizzare qualcosa di questo tipo:

var ElementoForm = new function(param_etichetta, param_privato)
{
    this.etichetta = param_etichetta;       // parametro interno pubblico
    this.privato = param_privato;
    var attributo_privato = "Io sono accessibile solo all'interno della classe";
    this.aggiorna = function()
    {
        return "Aggiorno l'etichetta: " + this.etichetta + "\nElemento privato: " + this.privato;
    }

In questo caso il new è già inserito nell'espressione di assegnazione del riferimento all'elemento funzione. Questo comporta altresì che la classe non può più essere istanziata in altro modo e quindi questo è un possibile modo di implementare il design pattern Singleton.

L'operatore this e il Lexical Scoping

L'operatore this, come in Java, serve per associare le proprietà ed i metodi all'oggetto che andremo a creare mediante la classe. Con la parola this intenderemo questo oggetto e con l'operatore punto andremo ad accedere agli attributi o ai metodi della classe. Quando vorremo invocare il metodo aggiorna() della classe ElementoForm andremo semplicemente a richiamarlo tramite ElementoForm.aggiorna(). Il metodo aggiorna(), è bene specificarlo, non è una funzione richiamabile in modo globale, ma è legato alla classe ElementoForm tramite la parola chiave this. Se avessimo omesso la parola chiave this quello che adesso è un metodo sarebbe stata una funzione globale.

Bisogna però fare molta attenzione nell'utilizzare liberamente la parola chiave this perché si potrebbe facilmente cadere in errore ed avere come errore degli undefined. Di seguito un esempio concreto.

var Persona = {
  nome: "Edoardo",
  eta: 28,
  saluta: function () {
    return "Ciao " + this.nome + ".  Ma davvero hai " + this.eta + " anni?";
  }
};

Persona.saluta(); //tutto funziona regolarmente

Questo modello di utilizzo del this e dello scope all'interno di oggetti, in questo caso definiti come oggetti JSON, funziona limitatamente a questo tipo d'utilizzo. Nascono dei problemi quando si fa qualcosa del genere:

var funzioneSaluta = Persona.saluta;
funzioneSaluta(); //il this non può essere risolto

Come risultato avremo:

=> 'Ciao undefined. Ma davvero hai undefined anni?'

Questo risultato è derivante dal fatto che Javascript non ha modificatori d'accesso e possiamo accedere liberamente ai membri delle classi senza alcuna reale limitazione. Inoltre lo scope delle variabili è di tipo lessicale (statico), quindi avremo uno scope dipendente da quando viene interpretato dall'analizzatore lessicale e indipendente dal call stack. Al contrario dei linguaggi con dynamic scoping, che tengono traccia del data flow tramite i record di attivazione sullo stack delle variabili e delle funzioni, JS mantiene un flusso dei dati che rimane dunque statico.

Nel caso sopra lo scope del this viene interpretato da JavaScript come quello globale e le variabili nome ed eta non possono essere trovate. Per questo motivo è bene limitare questa "apertura" e disponibilità nell'accesso ai membri delle classi, delle funzioni e in generale di quelle variabili che possono essere liberamente lette, modificate o addirittura eliminate. La soluzione a questo problema è rappresentata dalle closures (chiusure), che manterranno all'interno delle chiamate a funzione tutti i riferimenti necessari e non consentiranno di disaccoppiare così la funzione dall'ambiente della chiamata.

Per semplicità si può pensare al this come qualsiasi cosa venga prima del punto nella chiamata alla funzione. Se non c'è nulla prima della chiamata a funzione verrà allora invocato lo scope globale e a quel punto la frittata sarà fatta.


Creare le classi JS utilizzando la sintassi JSON

JSON è un acronimo di JavaScript Object Notation e consente di costruire gli oggetti in modo differente rispetto a quanto visto finora (a partire da JavaScript 1.2). La sintassi utilizzata con questa tecnica viene definita "eval-notation", dove c'è un'assegnazione chiave valore che potrebbe ricordare la costruzione di un vettore associativo. Vediamo come creare un oggetto vuoto utilizzando la notazione JSON:

var o = {};     // Notazione JSON

Per richiamare il costruttore di un oggetto si poteva anche chiamare il costruttore di base che è costituito da Object().

var o = new Object();

Anche per la definizione degli array è possibile utilizzare la sintassi JSON:

var a = [];

invece della classica:

var a = new Array();

Utilizzando JSON è così possibile definire una classe e al contempo crearne un'istanza. Questo induce anche l'utilizzo del pattern Singleton per cui si ha la possibilità di avere esclusivamente quell'istanza della data classe. Vediamo come creare una classe che descriva la Apple Computers con JSON:

var Apple = {
    ceo: "Steve Jobs",
    type: "company",
    state: "USA",
    getInfo: function () {
        return this.type + ': ' + 'apple. CEO: ' + this.ceo;
    }
}

In questo modo la classe esiste già ed è istanziata tramite il riferimento Apple. Se si volesse modificare un suo attributo o richiamarne un metodo basterebbe:

apple.ceo = "Bill Gates :P";
alert(apple.getInfo());

Un altro esempio di sintassi JSON:

var myobject = {
   method: function(){
   alert("foo");
   }
}

window.onload = function(){
myobject.method();
}

La parola chiave var

Con la parola chiave var si definisce in modo sicuro lo scope di una data variabile. Questo risulta fondamentale per non rischiare di adombrare un determinato parametro all'interno dello scope di un blocco o di una funzione. Vediamo un esempio per capire, tramite il codice, questo concetto.


var scope_test = "Variabile Globale";	// una variabile globale non ha bisogno della parola var 
					                        // ma non è nemmeno sbagliato usare la parola var per dichiarare una globale
function Prova(){

	scope_test = "This is a Fringe World!";                 // parametro privato senza var

	this.readParam = function() {
		alert(scope_test);
	};
};

alert(scope_test);		// Variabile Globale
var prova = new Prova();
prova.readParam();		// >> This is a Fringe World!
alert(scope_test);		// >> This is a Fringe World!

Perché mai alla fine scope_test corrisponde alla stringa "This is a Fringe World!"? Abbiamo in qualche modo adombrato un riferimento a scope_test, ma come? Semplicemente non abbiamo utilizzato la parola chiave var all'interno della classe Prova, quindi abbiamo di fatto concesso uno scope globale a scope_test. La risoluzione è semplicemente l'utilizzo di var all'interno delle parentesi graffe che definiscono il costruttore della classe Prova.

Risalire alla variabile globale da uno scope locale

Può capitare di dover riuscire a creare un riferimento ad una variabile globale che abbia lo stesso nome di una locale. A primo impatto si può pensare di essere costretti a eseguire un cambio di tutti i nomi della variabile definiti localmente, ma ciò non è effettivamente vero. Basta solo indicare con chiarezza una "linea ereditaria" certa per la data variabile/attributo globale. Se noi abbiamo la nostra classe Prova possiamo intendere un parametro come Prova.parametro, così per un parametro globale basterà andare a richiamare window.parametro, in quanto window è la superclasse da cui ereditano tutti gli oggetti JS.

var scope_test = "Variabile Globale";	// una variabile globale non ha bisogno della parola var 
					                        // ma non è nemmeno sbagliato usare la parola var per dichiarare una globale
function Prova(){

	var scope_test = "This is a Fringe World!";                 // parametro privato

	this.readParam = function() {
		alert(scope_test);
	};

       this.readGlobalParam = function() {
		alert(window.scope_test);                      // questa volta verrà letta la variabile globale e non quella di classe
	};

};

Tutto viene ereditato da window ad eccezzione delle top level functions (eval, encodeURI, escape, encodeURIComponent, alert... e poco altro) a prescindere che si utilizzi JavaScript con le classi che non lo si utilizzi in questo modo, si sta comunque sviluppando con un linguaggio Object Oriented in quanto una funzione globale sarà comunque window.funzioneGlobale(). La morale della favola è che estendiamo comunque un super oggetto e poi applichiamo a questa sintassi il paradigma di programmazione funzionale.

Definire un metodo a scope locale

Volendo definire un metodo privato, non avendo a disposizione una parola chiave apposita fornitaci dal linguaggio dovremo andare ad utilizzare semplicemente l'adombramento di un riferimento funzionale tramite lo scope. In pratica andremo a definire il riferimento a tale chiamata a funzione come qualcosa di richiamabile solo all'interno della classe e non dall'esterno, perché effettivamente invisibile.

function ClasseConMetodoPrivato(){
	
        /* Questo metodo non ha un riferimento a this quindi è invocabile come metodo locale - definito dentro il costruttore della classe */
	function scopeMethod(){alert("Ok mi hai invocato dalla classe. :) ")};      // "metodo" privato / funzione privata
	
	this.scopeMethod = function() {
                /* La funzione viene invocata con un artifizio */
		scopeMethod();                                           
	};
	
	this.mostraAvviso = function(msg) {
		
		function scopeMethod(){alert(msg)};                           // metodo locale definito nello scope del metodo mostraAvviso()
		scopeMethod();                                                // Richiama il metodo locale e non quello del costruttore!
	};
};
var test = new Test();
test.mostraAvviso("Hi everybody!");	         // Hi everybody!
test.scopeMethod();				// Ok mi hai invocato dalla classe. :)

Risorse