Guida Javascript: tecniche avanzate
Transcript
Guida Javascript: tecniche avanzate
Guida Javascript: tecniche avanzate di: Alberto Bottarini 1. Introduzione Presentazione della guida e degli argomenti trattati Lo scope 2. Cos'è lo scope Il concetto di scope nel contesto della programmazione in Javascript 3. Funzioni intercambiabili in base allo scope Un esempio di creazione di funzioni intercambiabili 4. I contesti e l'oggetto ''this' Analisi dei contesti in cui può essere evocata una funzione 5. I contesti e le funzioni 'callback' degli eventi L'importanza delle funzioni 'callback' per il miglioramento del codice 6. Scope vs. OOP Un confronto con la programmazione ad oggetti 7. L'oggetto Function e i metodi apply e call Analisi di due metodi fondamentali dell'oggetto Function 8. Controllare lo scope di esecuzione di una funzione Un metodo utile per superare le mancanze dell'API standard di Javascript Le closure 9. Il concetto di scope chain Un'introduzione alle closure e il concetto di scope chain 10. Isolare l'esecuzione delle funzioni Uno degli aspetti più interessanti legati alla closure 11. Contestualizzare le closure Analisi di un ambito in cui applicare con successo le closure 12. Temporizzare le funzioni Come superare il problema della temporizzazione nell'esecuzione di particolari funzioni 13. Il memory-leak di Internet Explorer Come superare uno dei più fastidiosi bug del browser di Microsoft La programmazione ad oggetti 14. Perché programmare ad oggetti Vantaggi e benefici di questo approccio alla programmazione in ambito Javascript 15. Il concetto di prototipo Definizione di prototipo nel contesto di Javascript 16. La classe DataGrid Una classe di esempio per mettere in pratica i concetti fin qui appresi 17. Costruire un componente come DataGrid Analisi della classe DataGrid e sua implementazione 18. L'ereditarietà in JavaScript Analisi di uno degli aspetti più funzionali della programmazione ad oggetti 19. Scrivere una classe estendibile Impariamo ad estendere classi e componenti Module pattern 20. Cosa significa utilizzare un pattern Introduzione al concetto di pattern 21. Definire una classe secondo il module pattern Affrontare il problema della visibilità delle proprietà e dei metodi all'interno di un oggetto 22. Logica e scope dei membri Analizzare una funzione per capirne la struttura 23. Dichiarare membri privati Un aspetto importante nella logica di funzionamento di una funzione 24. Variazioni del pattern Estensioni significative del module pattern Lazy loading 25. Il concetto di Lazy Loading Cos'è e quando serve il lazy loading 26. Due diversi approcci al Lazy Loading Ajax Lazy Loading (ALL) e DOM Lazy Loading (DLL) 27. Analizziamo una libreria La libreria Lazy Loader in pratica 28. Lazy Loading in azione Mettiamo in pratica i concetti base appresi su questa tecnica Introduzione (versione per la stampa) | Guide JavaScript | Javascript.... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4338/introduzione/ Introduzione Questa guida alle tecniche avanzate di Javascript si occuperà di analizzare alcune caratteristiche chiave del linguaggio. Sono caratteristiche che una volta comprese potranno fornire allo sviluppatore tutta una serie di vantaggi. Da un lato faciliteranno lo sviluppo: si potranno infatti realizzare strutture e algoritmi più snelli e più rapidi da scrivere. Dall'altro lato si ottiene il risultato di migliorare la stabilità dell'applicazione, la quale potrà contare su una struttura portante più solida e manutenibile. Le lezioni, nonostante presentino argomenti prettamente teorici, verranno accompagnate, quando possibile, da esempi che chiariranno al meglio gli aspetti trattati. Nello specifico, gli argomenti trattati saranno i seguenti: La tematica dello scope: aspetto fondamentale del linguaggio che permette di ridurre in modo drastico le linee di codice sia perché diminuiscono i parametri che vengono in qualche modo scambiati tra le diverse componenti sia perché è possibile eliminare qualsiasi duplicato di funzione cambiando semplicemente l'ambiente nella quale essa viene eseguita. Le closure: una closure può essere definita come una espressione o una funzione che può accedere a particolari variabili presenti in un ambiente ben delimitato che appunto "racchiude" la funzione. é un concetto molto discusso soprattutto recentemente visto l'enorme successo di JavaScript, linguaggio che interpreta perfettamente questa definizione. Le maggiori potenzialità di questa tecnica emergono infatti proprio nel linguaggio di scripting principe nell'epoca di AJAX e delle richieste asincrone effettuate da pagine web. La programmazione ad oggetti: rappresenta una metodologia di sviluppo software che si contrappone alla programmazione procedurale, spesso troppo poco gestibile e confusionaria. Il module pattern: un pattern che permette, tramite una tecnica particolare, di poter usufruire dei modificatori di visibilità anche in JavaScript. Il Lazy Loading: una particolare tecnica che cerca di risolvere il problema delle performance in applicazioni AJAX, problema sentito soprattutto su connessioni non a banda larga. Versione originale: http://javascript.html.it/guide/lezione/4338/introduzione/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 12/06/2012 21:52 Cos'è lo scope (versione per la stampa) | Guide JavaScript | Javascript... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4339/cose-lo-scope/ Cos'è lo scope Una traduzione di scope potrebbe essere quella di contesto di esecuzione all'interno del quale una particolare funzione JavaScript viene eseguita. Questo è un concetto abbastanza nuovo per chi proviene da qualche altro linguaggio di programmazione: nei linguaggi funzionali non ha senso parlare di contesto di esecuzione in quanto le diverse funzioni risiedono nello stesso grande contenitore globale, mentre nei linguaggi orientati agli oggetti, nonostante esista un ambiente all'interno del quale ciascun metodo viene invocato, esso non può essere in qualche modo gestito in quanto è sempre rappresentato dall'oggetto stesso sul quale il metodo viene chiamato. In JavaScript qualsiasi funzione ha uno scope che può essere referenziato tramite la keyword this. Questo costrutto interno al linguaggio serve appunto per fare riferimento al macro-oggetto all'interno del quale il metodo viene eseguito. Se una determinata funzione non presenta uno scope preciso, essa verrà invocata all'interno del oggetto globale window, che, come sappiamo, è uno degli oggetti impliciti del motore di interpretazione di JavaScript quando viene eseguito all'interno di un browser e che rappresenta la finestra aperta dall'utente. Vediamo subito un esempio: <script type="text/javascript"> var funzioneGlobale = function() { alert(this); // oggetto implicito window } var container = { funzioneLocale: function() { alert(this); //oggetto container } } funzioneGlobale(); container.funzioneLocale(); </script> Abbiamo creato due funzioni, una globale e una definita come membro di un oggetto. Nonostante il contenuto della funzione sia lo stesso, l'esito non sarà tale. Infatti funzioneGlobale() viene eseguita all'interno dell'oggetto window mentre funzioneLocale() all'interno del nostro oggetto container. Per questo motivo lo scope tra le due funzioni sarà diverso. Fin qua sembra tutto facile. È tempo di fare un passo avanti. Versione originale: http://javascript.html.it/guide/lezione/4339/cose-lo-scope/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 12/06/2012 21:52 Funzioni intercambiabili in base allo scope (versione per la stampa) | ... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4340/funzioni-intercamb... Funzioni intercambiabili in base allo scope Una delle tante possibilità offerte da questo aspetto del linguaggio è quello di creare funzioni "intercambiabili" in base allo scope. Approfondiamo con un esempio: <script type="text/javascript"> var stampaNome = function() { alert(this.nome || "Non ho nessun nome"); } var persona = { nome: "Alberto", stampaNome: stampaNome } var animale = { razza: "Pastore tedesco", stampaNome: stampaNome } persona.stampaNome(); animale.stampaNome(); </script> In questo esempio un po' banale è possibile vedere come la stessa funzione può essere in qualche modo condivisa anche da oggetti molto diversi tra loro ma referenziati al suo interno sempre tramite this. Versione originale: http://javascript.html.it/guide/lezione/4340/funzioni-intercambiabili-in-base-allo-scope/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 12/06/2012 21:53 I contesti e l'oggetto ''this' (versione per la stampa) | Guide JavaScript |... 1 di 2 http://javascript.html.it/guide/stampa_lezione/4341/i-contesti-e-loggett... I contesti e l'oggetto ''this' Esistono quattro diversi contesti entro il quale una particolare funzione può essere invocata e quindi esistono quattro differenti comportamenti che possono essere assunti dall'oggetto this. Vediamo i primi due. Invocazione di un metodo di un particolare oggetto In una organizzazione del codice orientata agli oggetti, c'è la spesso la necessità, all'interno di un metodo, di identificare e di riferirsi all'oggetto sul quale il metodo è stato invocato. <script type="text/javascript"> var persona = { nome: "Alberto", cognome: "Bottarini", stampaNomeCognome: function() { alert(this.nome + "\n" + this.cognome) } } persona.stampaNomeCognome(); </script> Nell'esempio abbiamo creato un oggetto persona con due proprietà principali, nome e cognome. Se dall'interno di un metodo (per esempio il nostro stampaNomeCognome()) vogliamo far riferimento all'oggetto o a qualche sua proprietà utilizziamo this (o this.proprietà). Costruttore Sempre in ottica object-oriented, all'interno di un costruttore è possibile fare riferimento all'oggetto, che verrà creato grazie al costrutto new, utilizzando il nostro caro this. Riscriviamo l'esempio di prima in un modo più elegante: <script type="text/javascript"> var persona = function(nome, cognome) { this.nome = nome; this.cognome = cognome; this.stampaNomeCognome = function() { alert(this.nome + "\n" + this.cognome) } } var a = new persona("Alberto", "Bottarini"); a.stampaNomeCognome(); </script> Rispetto all'esempio di prima in questo secondo caso non abbiamo creato una particolare istanza dell'oggetto, 13/06/2012 16:57 I contesti e l'oggetto ''this' (versione per la stampa) | Guide JavaScript |... 2 di 2 http://javascript.html.it/guide/stampa_lezione/4341/i-contesti-e-loggett... ma abbiamo definito la classe persona, utilizzabile infinite volte per infinite istanze, le quali implementeranno tutte il metodo stampaNomeCognome(). Versione originale: http://javascript.html.it/guide/lezione/4341/i-contesti-e-loggetto-this/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 16:57 I contesti e le funzioni 'callback' degli eventi (versione per la stampa) |... 1 di 2 http://javascript.html.it/guide/stampa_lezione/4342/i-contesti-e-le-funz... I contesti e le funzioni 'callback' degli eventi Gli ultimi scenari relativi ai contesti sono forse quelli con il maggior potenziale di apprendimento e di miglioramento del codice. Come sappiamo JavaScript è un linguaggio event driven, nel quale è possibile eseguire funzioni in risposta ad una o più determinate azioni effettuate dall'utente grazie all'engine interno del linguaggio che invoca le corrette funzioni precedentemente assegnate. All'interno di queste funzioni callback (definite in questo modo perché vengono fatte "scattare" non dal programmatore ma dal motore interno) l'oggetto this assume un'importanza notevole. È necessario però introdurre una distinzione a livello di assegnazione di callback ad eventi. Esistono infatti due meccanismi diversi che presentano comportamenti successivi diversi. Se assegniamo la callback tramite un particolare comando JavaScript, la funzione verrà eseguita nel contesto dell'oggetto sul quale l'evento è scattato; se invece assegniamo la callback direttamente inline nell'HTML essa verrà eseguita in un contesto più grande: l'oggetto window. Approfondiamo con un esempio: <script type="text/javascript"> function controllaScope() { alert(this.tagName || "Niente tagName, sono window"); } window.onload = function() { document.getElementsByTagName("button")[1].onclick = controllaScope; document.getElementsByTagName("a")[1].onclick = controllaScope; } </script> <button onclick="controllaScope()">Bottone 1</button> <button>Bottone 2</button> <a href="#" onclick="controllaScope()">Link 1</a> <a href="#">Link 2</a> Nonostante la funzione sia uguale sia per bottoni che per le ancore, cambia il metodo di assegnamento di essa all'evento onclick e quindi cambia anche l'ambiente di esecuzione. Il primo bottone e la prima ancora verranno eseguiti all'interno di window (window.tagName ritorna null, quindi l'alert visualizzerà la stringa inserita manualmente); il secondo bottone e la seconda ancora stamperanno invece il proprio tagName (rispettivamente BUTTON e A). Oltre a queste due modalità di assegnamento di eventi, è consigliabile utilizzare un event framework come Prototype, JQuery (http://javascript.html.it/guide/leggi/168/guida-jquery/) o YUI per avere una gestione degli eventi più semplice e soprattutto cross-browser. Versione originale: http://javascript.html.it/guide/lezione/4342/i-contesti-e-le-funzioni-callback-degli-eventi/ 13/06/2012 16:58 I contesti e le funzioni 'callback' degli eventi (versione per la stampa) |... 2 di 2 http://javascript.html.it/guide/stampa_lezione/4342/i-contesti-e-le-funz... © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 16:58 Scope vs. OOP (versione per la stampa) | Guide JavaScript | Javascrip... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4343/scope-vs-oop/ Scope vs. OOP Fino a questo momento il concetto di scope sembra tutt'altro che difficile, anzi spesso risulta comodissimo per fare riferimento ad un oggetto senza dover ricorrere ai parametri da passare ad una funzione. Esiste però una situazione molto frequente in cui lo scope crea non pochi problemi allo sviluppatore inesperto. Essa riguarda l'assegnazione di un metodo di un particolare oggetto ad un evento. Vediamo subito un esempio: <script type="text/javascript"> var persona = { nome: "Alberto", cognome: "Bottarini", stampaNomeCognome: function() { alert(this.nome + "\n" + this.cognome) } } window.onload = function() { document.getElementsByTagName("button")[0].onclick = persona.stampaNomeCognome; } </script> <button>Bottone 1</button> In questo esempio ci aspettiamo un comportamento speculare a quello visto in precedenza, invece non è cosi. Questo perché nel momento dell'esecuzione del metodo stampaNomeCognome() lo scope non è piu l'oggetto persona (dal quale possiamo ottenere il nome e il cognome), bensì l'oggetto button che non ha né nome né cognome. L'esecuzione dello script in questo caso stamperà due undefined. Un problema del tutto simile è riscontrabile facendo un frequente uso di setTimeout() o setInterval(), funzioni per posticipare o ripetere nel tempo una determinata funzione o metodo. Non disperiamo più del necessario però: esiste una soluzione per risolvere questo scomodo problema. La vedremo nella prossima lezione. Versione originale: http://javascript.html.it/guide/lezione/4343/scope-vs-oop/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 16:59 L'oggetto Function e i metodi apply e call (versione per la stampa) | Gu... 1 di 2 http://javascript.html.it/guide/stampa_lezione/4344/loggetto-function-e... L'oggetto Function e i metodi apply e call JavaScript, come già sappiamo, considera tutto come un oggetto e quindi rappresenta in questo modo anche le funzioni (e quindi i metodi). Ciascuna funzione presenta infatti proprietà e metodi proprio come un normale oggetto. Il prototipo dell'oggetto Function presenta due metodi di fondamentale importanza che sono apply() e call(). Il loro comportamento è simile, presentano una semplice differenza nella gestione dei parametri. Essi permettono infatti di invocare una determinata funzione definendo quale sarà il suo scope passandoglielo come primo parametro. È possibile inoltre fornire ulteriori parametri che verranno in qualche modo "passati" alla funzione oggetto dell'invocazione. La differenza tra apply e call è proprio questa: apply() oltre all'oggetto scope accetta un vettore di parametri, mentre call() accetta un numero indefinito di parametri. Passiamo all'esempio: <script type="text/javascript"> var persona = { nome: "Alberto", eta: 25 } var cane = { nome: "Pluto", eta: 4 } function stampaNomeEdEta(moltiplicatore) { if(!moltiplicatore) moltiplicatore = 1; alert(this.nome + ":\n" + (this.eta*moltiplicatore) + " anni"); } stampaNomeEdEta.call(persona); // un anno di un cane corrisponde a sette anni uomo giusto?? stampaNomeEdEta.call(cane, 7); </script> In questo esempio è possibile vedere come la stessa funzione stampaNomeEdEta(moltiplicatore), venga invocata su diversi oggetti senza essere in qualche modo assegnata ad essi sotto forma di un metodo. Da notare nel primo caso l'assegnazione di 1 a moltiplicatore come valore di default. Una volta capito questo semplice meccanismo possiamo quindi ritornare al nostro problema di prima e sostituire il window.onload() in questo modo: window.onload = function() { document.getElementsByTagName("button")[0].onclick = persona.stampaNomeCognome.call(persona); } Niente di più sbagliato! Dobbiamo assegnare una funzione all'evento onclick, non eseguirla. In questo caso infatti l'utilizzo del metodo call() (o del metodo apply()) è errato in quanto esso esegue subito la funzione. 13/06/2012 16:59 L'oggetto Function e i metodi apply e call (versione per la stampa) | Gu... 2 di 2 http://javascript.html.it/guide/stampa_lezione/4344/loggetto-function-e... apply() e call() in questo caso allora non bastano, abbiamo bisogno di qualche altra struttura più complessa; struttura che non è presente di default in JavaScript ma che dobbiamo crearci noi. Versione originale: http://javascript.html.it/guide/lezione/4344/loggetto-function-e-i-metodi-apply-e-call/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 16:59 Controllare lo scope di esecuzione di una funzione (versione per la st... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4345/controllare-lo-scop... Controllare lo scope di esecuzione di una funzione Sfortunatamente non esiste una soluzione elegante inserita nell'API standard di JavaScript, ma è possibile creare una mini-funzione ad hoc per il nostro caso. Andremo ad implementare un nuovo metodo per l'oggetto Function che permetterà di avere un totale controllo sullo scope di esecuzione della funzione stessa. Grazie alle closure (che vedremo successivamente in questa guida) estendiamo l'oggetto implicito Function: Function.prototype.setScope = function(scope) { var f = this; return function() { f.apply(scope); } } In questo modo, un po' particolare, possiamo farci ritornare una funzione che verrà eseguita in un particolare scope passato come parametro. A differenza di apply (e quindi call) con il metodo setScope, la funzione non verrà eseguita, ma verrà in qualche modo creata una "copia" eseguibile in un determinato contesto diverso da quello di default. Andiamo quindi a modificare il nostro script precedente: window.onload = function() { document.getElementsByTagName("button")[0].onclick = persona.stampaNomeCognome.setScope(persona); } In questo modo l'invocazione di setScope creerà una nuova funzione (con scope uguale a persona) e associerà questa nuova funzione come callback per l'evento onclick. Quando l'utente cliccherà sul pulsante questa nuova funzione verrà eseguita non più nello scope di default (quindi l'oggetto button) ma nello scope definito in precedenza. Versione originale: http://javascript.html.it/guide/lezione/4345/controllare-lo-scope-di-esecuzione-di-una-funzione/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 16:59 Il concetto di scope chain (versione per la stampa) | Guide JavaScript | ... 1 di 2 http://javascript.html.it/guide/stampa_lezione/4346/il-concetto-di-scop... Il concetto di scope chain Da un punto di vista puramente semantico, una closure può essere definita come una espressione o una funzione che può accedere a particolari variabili presenti in un ambiente ben delimitato che, appunto, "racchiude" la funzione. È un concetto molto discusso soprattutto recentemente visto l'enorme successo di JavaScript, linguaggio che interpreta perfettamente questa definizione. Anche in altri linguaggi è presente questa notazione, ma le maggiori potenzialità di questa tecnica emergono proprio nel linguaggio di scripting principe nell'epoca di AJAX e delle richieste asincrone effettuate da pagine web. Il concetto di scope chain Ritornando alle lezioni sullo scope, possiamo riaffermare che qualsiasi espressione JavaScript (sia essa una funzione globale o un particolare metodo di un oggetto) viene eseguita in uno scope che rappresenta l'ambiente all'interno della quale essa risiede. Questa affermazione non è del tutto precisa. Infatti le espressioni non presentano un unico scope, bensì un insieme gerarchico di ambienti di esecuzione, identificato come scope chain, catena di scope. Ovviamente questa catena può essere composta da un unico scope e quindi la precedente frase risulta oggettivamente vera, ma non è sempre cosi: ci possono essere espressioni con scope chain di differenti ampiezze. Giusto per fare un primo esempio: <script type="text/javascript"> var global = "ciao"; function func() { var local1 = "come"; var inner = function() { var local2 = "va?"; alert(global + " " + local1 + " " + local2); } inner.call(); } window.onload = function() { document.getElementsByTagName("button")[0].onclick = func; } </script> <button>Bottone 1</button> In questo piccolo e banale esempio abbiamo una variabile globale, una variabile locale per la funzione func() e una variabile locale per la function inner(). L'esecuzione di inner (tramite il metodo call()) fa sì che essa venga eseguita all'interno dello scope rappresentato dalla funzione func() che a sua volta viene eseguita all'interno dello scope globale (che ricordo è l'oggetto window): viene a costruirsi una vera e propria catena. 13/06/2012 17:00 Il concetto di scope chain (versione per la stampa) | Guide JavaScript | ... 2 di 2 http://javascript.html.it/guide/stampa_lezione/4346/il-concetto-di-scop... La funzione inner quindi ha a disposizione sia variabili locali a se stessa (local2) sia variabili prese in prestito da qualsiasi scope facente parte alla catena (local1 e global). Questo ovviamente perché non ci sono conflitti di nomi; nel momento in cui due variabili avessero lo stesso nome, avrebbe priorità quella appartenente allo scope più "vicino" alla funzione eseguita: <script type="text/javascript"> var name = "globale"; function func() { var name = "locale di func()"; var inner = function() { var name = "locale di inner()"; alert(name); } inner.call(); } window.onload = function() { document.getElementsByTagName("button")[0].onclick = func; } </script> <button>Bottone 1</button> In questo caso leggeremmo "locale di inner()", in quanto essa è stata definita ad un livello il più vicino possibile alla funzione (praticamente proprio al suo interno). Versione originale: http://javascript.html.it/guide/lezione/4346/il-concetto-di-scope-chain/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:00 Isolare l'esecuzione delle funzioni (versione per la stampa) | Guide Jav... 1 di 2 http://javascript.html.it/guide/stampa_lezione/4347/isolare-lesecuzione... Isolare l'esecuzione delle funzioni L'aspetto però più interessante legato alle closure è quello di poter in qualche modo isolare l'esecuzione delle nostre funzioni e di crearle in una maniera del tutto dinamica e divertente allo stesso tempo. Supponiamo il caso in cui si abbia bisogno di definire una sorta di costante alla quale poi eseguire delle somme di altri numeri. Il programmatore JavaScript non esperto potrebbe utilizzare una variabile globale per la costante richiamandola ogni volta nella sua funzione di somma. La soluzione è sicuramente funzionale ma crea il problema delle variabili globali, che, come sappiamo, è sempre meglio evitare per evitare conflitti di qualsiasi tipo sia internamente alla nostra applicazione, sia nel momento in cui si volesse includerla in un progetto più grande. Una soluzione elegante a questo semplice problema potrebbe essere quello di utilizzare le closure in questo modo: <script type="text/javascript"> function creaSomma(num1) { var somma = function(num2) { return parseInt(num1) + parseInt(num2); } return somma; } function clickListener() { var somma1 = creaSomma(7) // la prima "costante" è 7 alert(somma1(2)); alert(somma1(5)); alert(somma1(23)); var somma2 = creaSomma(9) // la seconda "costante" è 9 alert(somma2(45)); alert(somma2(6)); alert(somma2(24)); } window.onload = function() { document.getElementsByTagName("button")[0].onclick = clickListener; } </script> <button>Bottone 1</button> In questo esempio abbiamo una funzione che crea al suo interno una funzione e la ritorna tramite return. In questo caso è possibile creare una funzione con uno scope ben preciso, all'interno del quale esiste solamente la variabile num1 definita in fase di creazione. Ogni invocazione successiva a somma1() o somma2() verrà eseguita in questo scope "dinamico" (che è ben diverso nel caso si esegua somma1() e somma2()) al quale verrà aggiunta 13/06/2012 17:00 Isolare l'esecuzione delle funzioni (versione per la stampa) | Guide Jav... 2 di 2 http://javascript.html.it/guide/stampa_lezione/4347/isolare-lesecuzione... la variabile num2 passata a questa funzione temporanea solo successivamente. Questa è sicuramente una situazione alquanto improbabile e soprattutto banale che ha un unico scopo formativo. Nella prossima lezione esamineremo un caso in cui la creazione di closure può realmente tornarci utile. Siamo di fronte ad uno strumento potente da un lato, ma anche pericoloso perchè abbastanza complesso da utilizzare. Versione originale: http://javascript.html.it/guide/lezione/4347/isolare-lesecuzione-delle-funzioni/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:00 Contestualizzare le closure (versione per la stampa) | Guide JavaScript |... http://javascript.html.it/guide/stampa_lezione/4348/contestualizzare-le... Contestualizzare le closure Metodi di oggetti come callback Un ambito nel quale possiamo utilizzare con successo le closure riguarda l'assegnazione di un metodo di un particolare oggetto come callback di un evento scatenato dal DOM. La soluzione proposta in precedenza utilizzava la funzione setScope per forzare lo scope di un particolare metodo nel momento in cui l'utente faceva scatenare l'evento. In questo modo però non è più possibile risalire allo scope "originale" che rappresenta l'oggetto DOM sul quale l'evento è scattato. Questo può essere utile in caso di funzioni callback dinamiche che possono agire su diverse tipologie di elementi DOM. Una possibile soluzione per avere a disposizione sia l'oggetto DOM che l'oggetto JavaScript potrebbe essere questa: <script type="text/javascript"> var listener = function() { this.message = "Hai cliccato su di me"; this.onclick = function() { var listener = this; return function() { alert("MESSAGGIO: " + listener.message + "\nTAGNAME: " + this.tagName); } } } var clickListener = new listener(); window.onload = function() { document.getElementsByTagName("button")[0].onclick = clickListener.onclick(); document.getElementsByTagName("a")[0].onclick = clickListener.onclick(); document.getElementsByTagName("img")[0].onclick = clickListener.onclick(); } </script> <button>Bottone 1</button> <a href="#">Link 1</a> <img src="http://html.it/common/img/logo2.gif"/> In questo esempio abbiamo definito la classe listener che presenta una proprietà contenente un semplice messaggio ed un metodo che crea una closure che verrà successivamente assegnata come callback. La parte più interessante dello script è la riga var listener = this;. Questa riga, nonostante sembri di una banalità incredibile, rappresenta l'essenza di tutto il programma. In questo modo creiamo un alias per riferirsi all'oggetto listener in modo da averlo a disposizione anche all'interno della closure; così facendo evitiamo che this venga "sovrascritto" dallo scope più interno che in questo caso è rappresentato dall'oggetto DOM scatenante l'evento. All'interno della closure possiamo infatti riferirci al listener corrente tramite listener e all'oggetto DOM tramite this. Questo è un classico esempio di scope mixing, ovvero la possibilità di avere a disposizione nella stessa funzione diversi livelli della scope chain. 1 di 2 13/06/2012 17:01 Contestualizzare le closure (versione per la stampa) | Guide JavaScript |... http://javascript.html.it/guide/stampa_lezione/4348/contestualizzare-le... Versione originale: http://javascript.html.it/guide/lezione/4348/contestualizzare-le-closure/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 2 di 2 13/06/2012 17:01 Temporizzare le funzioni (versione per la stampa) | Guide JavaScript | ... http://javascript.html.it/guide/stampa_lezione/4349/temporizzare-le-fu... Temporizzare le funzioni Uno dei principali ostacoli nello sviluppo di rich application usando JavaScript riguarda la temporizzazione, ovvero l'esecuzione di particolari funzioni in automatico su base temporale. Le API del motore di interpretazione presentano una funzione per questi scopi: setTimout() (la problematica in questione riguarda anche setInterval()), funzione che riceve due parametri: una funzione e un numero di millisecondi. In questo modo JavaScript eseguirà in maniera automatica la funzione ritardandola di una quantità di millisecondi definita dal programmatore. La problematica riferita a questa funzione riguarda l'impossibilità di passare parametri a questa funzione ritardata rendendola di fatto poco usabile (soprattutto perché verrà eseguita in qualunque caso nello scope globale window). Con le closure invece possiamo intervenire sulla scope chain della funzione, permettendole di accedere a particolari parametri definiti in fase di creazione della funzione. Passiamo all'esempio: <script type="text/javascript"> var nameManager = function(name) { this.name = name; this.execute = function() { var manager = this; return function() { alert("parametro: " + manager.name + "\nthis.name: " + this.name); } } } var nm = new nameManager("Alberto"); window.onload = function() { window.setTimeout(nm.execute(), 1000); } </script> Grazie alle closure possiamo appunto intervenire sulla scope chain influenzando il secondo scope della catena accessibile appunto tramite la variabile manager definita durante l'esecuzione della funzione execute e non posticipata dal setTimeout(). Versione originale: http://javascript.html.it/guide/lezione/4349/temporizzare-le-funzioni/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 1 di 1 13/06/2012 17:01 Il memory-leak di Internet Explorer (versione per la stampa) | Guide J... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4350/il-memory-leak-di-... Il memory-leak di Internet Explorer Il browser di casa Microsoft, fino alla release 6 (almeno fino ad oggi non è stato scoperto nessun caso particolare sulle versioni 7 e 8), presentava un corposo bug che limitava le sue funzionalità quando gli oggetti istanziati nel browser raggiungevano un numero elevato. Questo perché il garbage collector di Internet Explorer non liberava la memoria per quegli oggetti (in particolare oggetti DOM e ActiveX) che non erano più referenziati in quanto non analizzava in maniera ottimale eventuali riferimenti circolari tra gli oggetti. Perché parlarne in questo articolo sulle closure? Semplicemente perché utilizzando questa tecnica è possibile incappare in questo tipo di problematica in IE. Sinteticamente, ogni qual volta un oggetto non viene più referenziato da nessun'altra entità presente nell'applicazione, la memoria occupata per quel particolare oggetto viene liberata appunto da un componente, il garbage collector. Quest'ultimo deve anche analizzare riferimenti incrociati e nel caso di oggetti isolati dal resto dell'applicazione deve liberare ulteriore memoria (se A è l'unico oggetto che fa riferimento a B, B l'unico a fare riferimento a C e C l'unico a fare riferimento ad A, tutti e tre gli oggetti possono essere eliminati). Il garbage collector di IE non controlla i riferimenti incrociati di tutti gli oggetti e non libera quindi tutta la memoria potenzialmente non utilizzata creando rallentamenti e ritardi. Un eventuale soluzione per risolvere situazioni in cui i riferimenti circolari sono l'unica strada da seguire è quella di utilizzare l'evento onunload per eliminare l'associazione tra evento e funzione callback tramite l'assegnazione di null. Questa sul memory-leak è stata una semplice introduzione del problema che però può essere evitato con un minimo di attenzione e di buona programmazione. Nel prossimo capitolo metteremo insieme le competenze acquisite in questo e nel precedente articolo per capire come utilizzare la programmazione Object-Oriented in JavaScript. Versione originale: http://javascript.html.it/guide/lezione/4350/il-memory-leak-di-internet-explorer/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:02 Perché programmare ad oggetti (versione per la stampa) | Guide JavaSc... http://javascript.html.it/guide/stampa_lezione/4351/perche-programma... Perché programmare ad oggetti La programmazione ad oggetti rappresenta una metodologia di sviluppo software che si contrappone alla programmazione procedurale, spesso troppo poco gestibile e confusionaria. Tramite l'approccio orientato ad oggetti è possibile definire delle classi che rappresentano un tipo di dato gestito dall'applicazione che può essere utilizzato solo tramite particolari interfacce esposte dalla classe stessa verso le altre classi dell'applicazione. All'interno di una classe esistono infatti alcune funzionalità "interne" che non vengono mostrate all'esterno. Tramite questa tipologia di programmazione si migliora notevolmente il codice limitando le interferenze tra diversi componenti e incapsulando funzionalità specifiche all'interno di una sorta di contenitore che permette una organizzazione più funzionale ed elegante. Le caratteristiche principali di un linguaggio ad oggetti sono tre: ereditarietà: la possibilità di estendere classi con altre classi che ne ereditano appunto proprietà e metodi; incapsulamento: la possibilità di includere in una classe funzionalità specifiche che non verranno rese pubbliche al resto dell'applicazione; polimorfismo: la possibilità di avere funzionalità particolari che si comportano in maniera diversa in base all'oggetto invocante o ai parametri ad esso passati. JavaScript è un linguaggio ad oggetti abbastanza particolare. Presenta infatti molte caratteristiche chiave della programmazione ad oggetti (per esempio il fatto che qualsiasi variabile sia un oggetto) ma non tutte (come per esempio la modifica della visibilità di un metodo). Inoltre presenta moltissimi aspetti facenti capo alla programmazione procedurale (la possibilità di avere funzioni "sganciate" da classi particolari) e aspetti un po' esotici come il concetto di scope e di closure visti nelle lezioni precedenti). Personalmente preferisco puntare sempre su una programmazione ad oggetti in quanto la ritengo più chiara, manutenibile ed elegante, rispetto ad una programmazione funzionale. Le mancanze presenti nel linguaggio non devono spaventare lo sviluppatore orientato agli oggetti in quanto possono comunque essere in qualche modo aggirate con un po' di mestiere e di buona programmazione. L'unica effettiva mancanza che ho riscontrato nel linguaggio è quella delle interfacce, componente abbastanza comodo nella programmazione ad oggetti. Versione originale: http://javascript.html.it/guide/lezione/4351/perche-programmare-ad-oggetti/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 1 di 1 13/06/2012 17:38 Il concetto di prototipo (versione per la stampa) | Guide JavaScript | Ja... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4352/il-concetto-di-proto... Il concetto di prototipo JavaScript presenta una programmazione ad oggetti basata sui prototipi. Questo concetto si differenzia un po' dai principali programmi orientati a questo tipo di programmazione com Java o C++. Il suo funzionamento è semplice. Ciascuna classe presenta al suo interno una proprietà, chiamata appunto prototype, che presenta un 'meta oggetto'. Qualsiasi oggetto facente capo a questa classe sarà istanziato esattamente come una copia di questo prototipo. Modificando questa meta classe sarà possibile influenzare direttamente tutti i precedenti oggetti creati a partire da questa classe. Passiamo subito ad una dimostrazione pratica: <script type="text/javascript"> function init() { var oggetto = function() { this.name = "alberto"; } var a = new oggetto(); oggetto.prototype.lastname = "bottarini"; var b = new oggetto(); alert("DATI DI A:\n"+a.name+"\n"+a.lastname); alert("DATI DI B:\n"+b.name+"\n"+b.lastname); } window.onload = init; </script> Innanzitutto creiamo la classe oggetto definendo solo il costruttore che imposta la proprietà name. Successivamente instanziamo un oggetto (a) e modifichiamo l'oggetto prototype aggiungendo una nuova proprietà: lastname. Per ultimo instanziamo un secondo oggetto (b) e stampiamo i parametri. Nonostante possa sembrare strano, anche l'oggetto a presenta una proprietà lastname proprio perché entrambi gli oggetti fanno capo a questo prototipo definito nella proprietà prototype della classe oggetto. Questa è una notevole comodità del linguaggio: è infatti possibile realizzare delle vere e proprie classi dinamiche che possono essere modificate internamente anche a runtime. Versione originale: http://javascript.html.it/guide/lezione/4352/il-concetto-di-prototipo/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:39 La classe DataGrid (versione per la stampa) | Guide JavaScript | Javasc... 1 di 2 http://javascript.html.it/guide/stampa_lezione/4353/la-classe-datagrid/ La classe DataGrid Per esaminare tutti gli aspetti più critici della programmazione ad oggetti in JavaScript, utilizziamo un esempio, cui faremo riferimento in tutto il capitolo. Vediamo come creare un semplice componente DataGrid con poche funzionalità utili allo scopo. Una DataGrid è caratterizzata da due vettori: il primo rappresenta il nome delle colonne e il secondo contiene a sua volta altri vettori che rappresentano i dati effettivi da mostrare. Presenta alcuni aspetti grafici come l'effetto rollover sulla riga sottostante il cursore del mouse e la possibilità di selezionare dei record. Gli unici due metodi esposti sono getSelectedIndex che ritorna un vettore contenente gli indici delle righe selezionate, e getSelectedData che ritorna un vettore contenente i dati contenuti nelle righe selezionate. Per nascondere eventuali proprietà e metodi privati è stata utilizzata la naming convention che prevede di chiamare gli oggetti con un underscore (_) davanti al nome. Ecco una parte del codice (versione integrale (http://www.html.it/guide/esempi/jsavanzato/esempi /classe_datagrid.html)). function DataGrid(header, data) { this._header = header; this._data = data; this._selectedIndex = []; } DataGrid.prototype.render = function(to) { var table = document.createElement("TABLE"); var thead = document.createElement("THEAD"); var trHead = document.createElement("TR"); for(var i = 0; i<this._header.length; i++) { var th = document.createElement("TH"); th.appendChild(document.createTextNode(this._header[i])); trHead.appendChild(th); } thead.appendChild(trHead); table.appendChild(thead); var tbody = document.createElement("TBODY"); for(var i = 0; i<this._data.length; i++) { var trBody = document.createElement("TR"); trBody.onmouseover = this._onmouseover.setScope(this, trBody, i); trBody.onmouseout = this._onmouseout.setScope(this, trBody, i); trBody.onclick = this._onclick.setScope(this, trBody, i); for(var c = 0; c<this._data[i].length; c++) { var td = document.createElement("TD"); 13/06/2012 17:39 La classe DataGrid (versione per la stampa) | Guide JavaScript | Javasc... 2 di 2 http://javascript.html.it/guide/stampa_lezione/4353/la-classe-datagrid/ td.appendChild(document.createTextNode(this._data[i][c])); trBody.appendChild(td); } tbody.appendChild(trBody); } table.appendChild(tbody); to.appendChild(table); } // event handler privati DataGrid.prototype._onmouseover = function(tr, i) { /* ... */ } DataGrid.prototype._onmouseout = function(tr, i) { /* ... */ } DataGrid.prototype._onclick = function(tr, i) { /* ... */ } DataGrid.prototype._onselect = function(tr, i) { /* ... */ } DataGrid.prototype._ondeselect = function(tr, i) { /* ... */ } // metodi esposti DataGrid.prototype.getSelectedIndex = function() { return this._selectedIndex; } DataGrid.prototype.getSelectedData = function() { var temp = []; for(var i = 0; i<this._selectedIndex.length; i++) temp.push(this._data[this._selectedIndex[i]]); return temp; } Versione originale: http://javascript.html.it/guide/lezione/4353/la-classe-datagrid/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:39 Costruire un componente come DataGrid (versione per la stampa) | Gu... 1 di 2 http://javascript.html.it/guide/stampa_lezione/4354/costruire-un-comp... Costruire un componente come DataGrid La classe DataGrid, che abbiamo definito nella lezione precedente, è definita da una semplice funzione e dalla modifica dell'oggetto prototype associato ad essa. Ovviamente per poter utilizzare questo approccio è necessario prima di tutto definire il costruttore e successivamente i metodi, in quanto l'oggetto DataGrid.prototype esiste solo successivamente alla definizione della funzione DataGrid. Il costruttore non fa altro che impostare alcune proprietà private della classe in base ai parametri passati e a istanziare il vettore _selectedIndex che conterrà gli indici delle righe selezionate. Il metodo più complesso è render il quale costruisce gli oggetti DOM che rappresentano la struttura della tabella ed appende questi oggetti all'elemento DOM ricevuto come parametro. Il punto critico di questo metodo è l'assegnazione a ciascuna riga della tabella (tr) di tre callback per altrettanti eventi (onmouseover, onmouseout, onclick). A queste funzioni callback viene forzato lo scope tramite la funzione setScope, alla quale vengono passati alcuni parametri aggiuntivi: nel nostro caso un riferimento all'oggetto tr stesso e l'indice della riga selezionata. Successivamente vengono definite queste funzioni callback, che modificarno l'aspetto grafico della tabella e, nel caso di _onclick, demandano le funzionalità a _onselect o a _ondeselect in base al fatto che la riga sulla quale l'utente ha cliccato sia già stata selezionata o meno (lo capiamo controllando che l'indice i sia presente o meno all'interno dell'oggetto _selectedIndex).Queste due funzioni a loro volta salvano o eliminano appunto da questa cache locale la riga selezionata. Gli ultimi due metodi sono quelli pubblici (non presentano l'underscore iniziale), che forniscono all'utilizzatore di questa classe un'interfaccia comoda per interagire e ottenere informazioni riguardo la griglia. Oltre a questa classe implementiamo altri metodi di utilità per gli oggetti Function e Array: <script type="text/javascript"> Function.prototype.setScope = function(obj) { var method = this; var arg = []; for(var i = 1; i < arguments.length; i++) arg.push(arguments[i]); temp = function() { return method.apply(obj, arg); }; return temp; } Array.prototype.exists = function(value) { for(var i = 0; i<this.length; i++) if(this[i] == value) return true; return false; } Array.prototype.remove = function(value) { for(var i = 0; i<this.length; i++) if(this[i] == value) this.splice(i,1); 13/06/2012 17:40 Costruire un componente come DataGrid (versione per la stampa) | Gu... 2 di 2 http://javascript.html.it/guide/stampa_lezione/4354/costruire-un-comp... } </script> Il metodo setScopeè la versione leggermente modificata della funzione vista in precedenza: questa richiama la funzione passando anche un numero non definito di argomenti passati alla funzione setScope. Il ciclo for iniza da 1 e non da 0 proprio per "scartare" il primo argomento che rappresenta lo scope e non un parametro aggiuntivo. Sono stati aggiunti anche due metodi di utilità all'oggetto implicito Array per ricercare se un elemento è già presente al suo interno e per eliminarlo. Il funzionamento della griglia è semplicissimo: <script type="text/javascript"> var grid; window.onload = function() { grid = new DataGrid(["FirstName", "LastName", "Age"], [ ["Luca","Luchi",32], ["Marco","Marchi",42], ["Bruno","Bruni",23] ]); grid.render(document.getElementsByTagName("div")[0]); } </script> <div></div> <button onclick="alert(grid.getSelectedData())">GetSelecteData</button> <button onclick="alert(grid.getSelectedIndex())">GetSelecteIndex</button> Oltre alla semplice creazione della griglia e il suo render all'interno del <div>, sono stati predisposti due bottoni per verificare l'effettivo funzionamento delle API della classe DataGrid. L'esempio (http://www.html.it/guide/esempi/jsavanzato/esempi/esempio_datagrid.html), come al solito, è perfettibile, sia sotto il profilo della funzionalità, sia nell'aspetto, ma dovrebbe essere sufficiente a mostrare la potenza dell'uso dei prototipi. Versione originale: http://javascript.html.it/guide/lezione/4354/costruire-un-componente-come-datagrid/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:40 L'ereditarietà in JavaScript (versione per la stampa) | Guide JavaScript |... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4355/lereditarieta-in-jav... L'ereditarietà in JavaScript Uno degli aspetti più funzionali e anche divertenti della programmazione ad oggetti è senza dubbio l'ereditarietà. Tramite essa è possibile infatti creare diverse classi tra di loro relazionate, di cui una estende le funzionalità presenti in un'altra. JavaScript presenta un'architettura un po' strana riguardo all'ereditarietà in quanto la supporta pienamente, ma non presenta alcuni strumenti utili per poterla utilizzare seriamente. Grazie ad alcune piccole possibile, possiamo emulare quei comportamenti che non sono stati inseriti come caratteristiche intrinseche del linguaggio. La funzione inherits Uno di questi "meccanismi mancanti" è quello di permettere di definire una classe come sottoclasse di un'altra. Questo può essere risolto con la funzione inherits: <script> Function.prototype.inherits = function(superclass) { var temp = function() {}; temp.prototype = superclass.prototype; this.prototype = new temp(); } </script> Con questa funzione basta scrivere ClasseFiglia.inherits(ClassePadre) per creare di fatto una ereditarietà. Tramite questa funzione è possibile inoltre invocare il costruttore padre all'interno del costruttore figlio semplicemente chiamando il nome della classe con i parametri necessari. Spesso quando si crea una classe non si immagina che qualcuno potrà poi estenderla, e quindi non si implementa il codice in questa ottica. Il nostro esempio di prima calza proprio a pennello in quanto nella situazione corrente è un po' difficile aggiungere nuove funzionalità alla DataGrid mantenendone la struttura interna. Questo è il classico errore nel quale incorre lo sviluppatore alle prime armi che non pensa all'estendibilità futura del codice. Nella prossima lezione vediamo un esempio di codice estensibile, proprio a partire dal nostro primo esempio. Versione originale: http://javascript.html.it/guide/lezione/4355/lereditarieta-in-javascript/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:40 Scrivere una classe estendibile (versione per la stampa) | Guide JavaScr... 1 di 3 http://javascript.html.it/guide/stampa_lezione/4356/scrivere-una-classe... Scrivere una classe estendibile Quando creiamo un componente (o comunque una classe) è buona norma realizzare più metodi, assegnando a ciascuno un lavoro specifico. Questo ci consente poi di effettuare l'override dei soli metodi che ci occorre modificare. Nel nostro esempio abbiamo problemi con la funzione render, che costruisce completamente la tabella non delegando task a nessun altro metodo e di fatto non permettendo di modificare niente, se non riscrivendo completamente il metodo (attività che a noi programmatori pigri non interessa). Una possibile ristrutturazione potrebbe portarci a scomporre il metodo render in due sottometodi createHeader e createBody: <script type="text/javascript"> DataGrid.prototype.render = function(to) { this.tableEl = document.createElement("TABLE"); this.tableEl.style.border = "1px solid black"; this.tableEl.style.borderCollapse = "collapse"; this.createHeader(); this.createBody(); to.appendChild(this.tableEl); } DataGrid.prototype.createHeader = function() { var thead = document.createElement("THEAD"); var trHead = document.createElement("TR"); for(var i = 0; i<this._header.length; i++) { var th = document.createElement("TH"); th.appendChild(document.createTextNode(this._header[i])); th.style.padding = "5px"; th.style.borderBottom = "1px solid black"; trHead.appendChild(th); } thead.appendChild(trHead); this.tableEl.appendChild(thead); } DataGrid.prototype.createBody = function() { var tbody = document.createElement("TBODY"); for(var i = 0; i<this._data.length; i++) { var trBody = document.createElement("TR"); trBody.onmouseover = this._onmouseover.setScope(this, trBody, i); trBody.onmouseout = this._onmouseout.setScope(this, trBody, i); trBody.onclick = this._onclick.setScope(this, trBody, i); 13/06/2012 17:41 Scrivere una classe estendibile (versione per la stampa) | Guide JavaScr... 2 di 3 http://javascript.html.it/guide/stampa_lezione/4356/scrivere-una-classe... for(var c = 0; c<this._data[i].length; c++) { var td = document.createElement("TD"); td.appendChild(document.createTextNode(this._data[i][c])); td.style.padding = "5px"; trBody.appendChild(td); } tbody.appendChild(trBody); } this.tableEl.appendChild(tbody); } </script> In questo modo il funzionamento non cambierà, ma sarà possibile estendere la classe DataGrid modificando il modo in cui viene costruito l'header. Con questo secondo approccio è stato necessario utilizzare una nuova proprietà (tableEl) come container per l'oggetto DOM rappresentato dalla tabella. Proviamo ora a creare utilizzando l'ereditarietà la IconDataGrid: griglia che presenta nell'header oltre che al testo anche una piccola icona. Sarà necessario modificare solamente il costruttore e il metodo createHeader: <script type="text/javascript"> function IconDataGrid(header, headerIcons, data) { this._headerIcons = headerIcons; DataGrid.call(this, header, data); } IconDataGrid.inherits(DataGrid); IconDataGrid.prototype.createHeader = function() { var thead = document.createElement("THEAD"); var trHead = document.createElement("TR"); for(var i = 0; i<this._header.length; i++) { var icon = this._headerIcons[i]; var th = document.createElement("TH"); th.appendChild(document.createTextNode(this._header[i])); th.style.padding = "5px"; th.style.borderBottom = "1px solid black"; th.style.background = "url("+icon+") no-repeat left"; th.style.paddingLeft = "14px"; trHead.appendChild(th); } thead.appendChild(trHead); this.tableEl.appendChild(thead); } </script> Le uniche righe degne di nota sono la chiamata al costruttore di DataGrid tramite il metodo call e l'utilizzo della funzione inherits definita in precedenza per gestire l'ereditarietà in maniera automatica. Le altre aggiunte sono banali: il salvataggio nella proprietà _headerIcons del vettore contenente i path per le immagini e l'aggiunta due semplici proprietà CSS all'elemento th (background e paddingLeft). Il gioco è fatto! Ecco qua l'esempio completo. (http://www.html.it/guide/esempi/jsavanzato/esempi/js_oop.zip) 13/06/2012 17:41 Scrivere una classe estendibile (versione per la stampa) | Guide JavaScr... 3 di 3 http://javascript.html.it/guide/stampa_lezione/4356/scrivere-una-classe... Ovviamente il codice è ancora ad uno stato abbastanza iniziale, la suddivisione dei compiti è solo parziale e l'introduzione di altre modifiche presenta gli stessi problemi visti in precedenza, ma tramite questo esempio abbiamo capito non solo la comodità dell'ereditarietà, ma anche l'importanza della progettazione del codice che deve essere sempre robusto e facilmente estendibile. Versione originale: http://javascript.html.it/guide/lezione/4356/scrivere-una-classe-estendibile/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:41 Cosa significa utilizzare un pattern (versione per la stampa) | Guide Ja... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4357/cosa-significa-utili... Cosa significa utilizzare un pattern Nel campo della programmazione e dello sviluppo di software, è frequente trovarsi di fronte a problemi in qualche modo diffusi per i quali spesso è già stata trovata una soluzione performante, efficiente e sicura del nostro problema. A differenza di una libreria software alla quale è possibile delegare parte di un lavoro richiamandone le API pubbliche e ottenendo il risultato dell'elaborazione, utilizzando un pattern non facciamo altro che "copiare" non parte del codice scritto da altri, ma bensì una tecnica per affrontare un problema diffuso che nel tempo si è consolidata come un buona approccio alla risoluzione dello stesso. Alcuni esempi di pattern possono essere ritrovati all'interno della macrocategoria degli algoritmi di ordinamento, argomento assai discusso ancora oggi. Esistono infatti delle tecniche per ordinare gli elementi numerici di un vettore che vengono implementate diversamente per ciascun linguaggio di programmazione (per esempio il bubble-sort, l'heap-sort e il merge-sort). Questi non sono nient'altro che algoritmi che spiegano, teoricamente, come procedere per ordinare un vettore numerico. Nonostante esistano moltissimi linguaggi di programmazione, tutti fanno capo alla stessa tecnica di ordinamento, allo stesso pattern. In questo capitolo analizzeremo un pattern abbastanza conosciuto dagli sviluppatori JavaScript, il module pattern. Versione originale: http://javascript.html.it/guide/lezione/4357/cosa-significa-utilizzare-un-pattern/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:42 Definire una classe secondo il module pattern (versione per la stampa) ... 1 di 2 http://javascript.html.it/guide/stampa_lezione/4358/definire-una-classe... Definire una classe secondo il module pattern Uno dei problemi che emergono utilizzando la programmazione ad oggetti in JavaScript è la mancanza del concetto di visibilità delle proprietà e dei metodi all'interno di un oggetto. Linguaggi come Java presentano alcuni costrutti base del linguaggio (public, private, protected) che permettono al programmatore di modificare la visibilità di un membro di una classe rendendolo: pubblico (cioè disponibile a qualsiasi altra classe) privato (richiamabile solo all'interno della classe stessa) protetto (una via di mezzo tra pubblico e privato, nel caso di Java accessibile solo dalle classi appartententi allo stesso package e alle sottoclassi) Il programmatore alle prime armi, spesso, pensa che assegnare una visibilità specifica a metodi e proprietà sia superfluo e, spesso, si limita a definire pubblici tutti i membri di un oggetto, per non avere problemi. Un atteggiamento da dimenticare! Affrontare questo argomento con cognizione di causa permette di creare classi ben organizzate e riutilizzabili senza problemi, garantendo una delle caratteristiche importanti della programmazione ad oggetti: l'incapsulamento. Possiamo fare in modo che le classi espongano solo alcune funzionalità specifiche (quelle per cui la classe è progettata). In questo modo tutta logica di implementazione, composta di metodi e funzioni "interne", può risultare invisibile. Il Module Pattern è nato quindi, per risolvere questa lacuna e usufruire dei modificatori di visibilità anche in JavaScript. È stato inizialmente proposto da Douglas Crockford ed è stato pubblicizzato soprattutto nel blog della libreria grafica Yahoo User Interface (http://yuiblog.com/blog/2007/06/12/module-pattern/) (YUI). Iniziamo esaminando lo scheletro di un esempio di implementazione di una classe secondo il pattern che stiamo studiando: MyFirstModule = function() { var proprietaPrivata = "Io sono una proprieta privata"; function metodoPrivato() { return "Io sono una funzione privata"; } return { proprietaPubblica: "Io sono una proprieta pubblica", metodoPubblico: function() { return "Io sono un metodo pubblico"; } } }(); var module = MyFirstModule; alert(module.proprietaPubblica); alert(module.proprietaPrivata); // undefined alert(module.metodoPubblico); alert(module.metodoPrivato); // undefined Cerchiamo ora di capire la logica di questo approccio. 13/06/2012 17:46 Definire una classe secondo il module pattern (versione per la stampa) ... 2 di 2 http://javascript.html.it/guide/stampa_lezione/4358/definire-una-classe... Versione originale: http://javascript.html.it/guide/lezione/4358/definire-una-classe-secondo-il-module-pattern/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:46 Logica e scope dei membri (versione per la stampa) | Guide JavaScript ... 1 di 2 http://javascript.html.it/guide/stampa_lezione/4359/logica-e-scope-dei... Logica e scope dei membri Tralasciamo per un attimo il contenuto della funzione MyFirstModule e analizziamo il metodo con cui è stata definita. Non viene utilizzata la classica notazione function() { ... } ma al suo posto viene utilizzata questa struttura: var funzione = function() { //definizione della classe }() La cosa interessante sono le parentesi tonde subito dopo la parentesi graffa di chiusura della classe. Questo significa che la funzione oltre ad essere chiamata viene invocata. Quindi in realtà dentro la variabile "funzione" non è presente la funzione appena scritta, ma il suo risultato. Giusto per capire il concetto ecco un breve esempio relativo a questo approccio: var firstFunction = function() { return 10; }; var secondFunction = function() { return 10; }(); alert(firstFunction); alert(secondFunction); Il primo alert stamperà il costrutto JavaScript "object" mentre il secondo mostrerà il 10: questo perchè avendo aggiunto le parentesi alla fine, all'interno della variabile secondFunction non viene salvata una funzione, bensì il suo risultato. Analizzando la classe MyFirstModule, notiamo anche la presenza di un return all'interno della definizione, return che permette di definire quali sono i membri pubblici. Tramite questo return infatti è possibile ritornare (istantaneamente perché la funzione viene invocata subito) un oggetto che conterrà appunto i membri pubblici. Analizziamo questo sotto-aspetto con un nuovo esempio: var function = function() { return { prop: "ciao", method: function() { return "ciao" } } }(); var obj = function; alert(obj.prop); alert(prop.method); Abbiamo semplicemente creato (ed invocato) una funzione che ritorna un particolare oggetto contenente una proprietà (prop) e una funzione (method). Versione originale: http://javascript.html.it/guide/lezione/4359/logica-e-scope-dei-membri/ 13/06/2012 17:46 Logica e scope dei membri (versione per la stampa) | Guide JavaScript ... 2 di 2 http://javascript.html.it/guide/stampa_lezione/4359/logica-e-scope-dei... © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:46 Dichiarare membri privati (versione per la stampa) | Guide JavaScript |... http://javascript.html.it/guide/stampa_lezione/4360/dichiarare-membri-... Dichiarare membri privati L'ultimo aspetto da considerare è la definizione dei membri privati. Essi non sono accessibili dall'esterno proprio perchè non sono contenuti nel mega oggetto ritornato dalla funzione principale (che, ricordiamo, viene subito invocata). Questi membri infatti hanno uno scope limitato ed esistono solo all'interno della funzione che definisce l'oggetto. Essi non possono essere disponibili proprio perché non c'è modo di riferirsi a questa funzione in quanto non viene salvata in nessuna variabile ma viene eseguita subito. Questi membri privati però sono accessibili all'interno di metodi e proprietà pubbliche in quanto condividono lo scope tra di loro. Ecco un esempio: var NameModule = function() { // variabile privata var name = "Alberto"; // metodo privato var getName = function() { return name; } // il modulo ritorna l'oggetto pubblico return { alertName: function() { alert(getName()); } } }(); var nm = NameModule; nm.alertName(); In questo breve esempio abbiamo creato una proprietà privata (name), un metodo privato (getName) che legge la proprietà privata e un metodo pubblico (alertName) che invoca la funzione JavaScript alert passandogli come parametro il risultato della funzione privata getName. Bisogna evidenziare il fatto che non abbiamo utilizzato la parola chiave 'this', per evitare problemi di scope. Infatti essa fa riferimento ad un contesto che è esterno al nostro oggetto, nel momento in cui invochiamo il metodo alertName(). Anche quando invochiamo il metodo privato getName() all'interno del metodo pubblico, evitiamo di utilizzare il costrutto this: il metodo getName non esiste all'interno dell'oggetto ritornato (che presenta un unico membro che è alertName), ma è accessibile direttamente in quanto facente parte dello scope di invocazione. È importante specificare che sia la proprietà name, sia la funzione getName devono essere precedute dalla parola chiave var, nella dichiarazione, altrimenti vengono considerate variabili globali (proprietà dell'oggetto window). In questo modo otteniamo ciò che desideriamo: la proprietà name e il metodo getName sono accessibili solamente dai membri pubblici della classe e non dall'esterno. Ci sarebbe da aggiungere che questi elementi, name e getName, sono sì privati rispetto allo scope esterno, ma rispetto agli scope annidati diventano privilegiati e per convenzione andrebbero scritte con un underscore (_) anteposto all'identificatore. 1 di 2 13/06/2012 17:47 Dichiarare membri privati (versione per la stampa) | Guide JavaScript |... http://javascript.html.it/guide/stampa_lezione/4360/dichiarare-membri-... Versione originale: http://javascript.html.it/guide/lezione/4360/dichiarare-membri-privati/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 2 di 2 13/06/2012 17:47 Variazioni del pattern (versione per la stampa) | Guide JavaScript | Java... http://javascript.html.it/guide/stampa_lezione/4361/variazioni-del-pattern/ 1 di 2 Variazioni del pattern Questo approccio per realizzare oggetti in JavaScript ha riscontrato notevole successo: molti sviluppatori hanno adottato questo pattern e ne hanno anche modificato alcuni aspetti per renderlo migliore. Nonostante non siano modifiche sostanziali alla tecnica, è utile esaminare alcune estensioni per completezza e curiosità. Per semplificare applichiamo tutte le estensioni all'esempio visto nelle lezioni precedenti (il NameModule). Module Pattern in the Pub La prima evoluzione del Module Pattern definisce all'interno della classe un oggetto di nome pub come un membro privato; membro che poi viene ritornato come pubblico al posto di una funzione anonima come visto in precedenza. Questo pattern permette di risolvere eventuali problemi di namespace che possono sussistere utilizzando una funzione anonima. Ecco un breve esempio: var PubNameModule = function() { var name = "Alberto"; var getName = function() { return name; }; var pub = { alertName: function() { alert(getName()); }; } return pub; }(); var nm = NameModule; nm.alertName(); Module Pattern Curry Spesso utilizzando il Module Pattern si espone un unico metodo pubblico di inizializzazione (spesso chiamato init()) di un certo componente grafico (per esempio l'inizializzazione di un form). Con questo pattern si evita l'invocazione di questo metodo evitando di ritornare un oggetto complesso ma l'unica e semplice funzione richiamabile poi dall'esterno: var NameModule = function() { var name = "Alberto"; var getName = function() { return name; }; return function() { alert(getName()); }; }(); NameModule(); Revealing Module Pattern Con questa estensione tutti i membri pubblici vengono trattati allo stesso modo dei membri privati. Sarà in fase 13/06/2012 17:47 Variazioni del pattern (versione per la stampa) | Guide JavaScript | Java... http://javascript.html.it/guide/stampa_lezione/4361/variazioni-del-pattern/ 2 di 2 di return che essi verranno "rivelati" e quindi ritornati. Ecco l'esempio: var NameModule = function() { var name = "Alberto"; var getName = function() { return this.name; }; var alertName = function() { alert(getName()); }; return { alertName: alertName // sveliamo alertName } }(); var nm = NameModule; nm.alertName(); Versione originale: http://javascript.html.it/guide/lezione/4361/variazioni-del-pattern/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:47 Il concetto di Lazy Loading (versione per la stampa) | Guide JavaScript ... http://javascript.html.it/guide/stampa_lezione/4362/il-concetto-di-lazy-... Il concetto di Lazy Loading Dall'avvento di tecniche client-side come AJAX, le pagine Web sono sempre più complesse e pesanti: parte dell'elaborazione è infatti a carico del client. AJAX ha trasformato dei semplici siti web in vere e proprie applicazioni che spesso rendono il lavoro più rapido una volta inizializzate, ma richiedono più tempo per poter scaricare tutti gli script necessari al loro funzionamento. Un esempio emblematico di questo fenomeno è Gmail, la celeberrima applicazione Web per la gestione di una casella di posta, che, seppur funzionale e innovativa, presenta tempi di caricamento sicuramente maggiori rispetto ad un classica mailbox IMAP presente qualche anno fa su qualsiasi portale Web. In queste ultime lezioni della guida discuteremo di una particolare tecnica che cerca di risolvere questo problema di performance, sentito soprattutto su connessioni analogiche non a banda larga. Il concetto Con Lazy Loading (caricamento pigro) si intende un download di script on demand, ovvero solamente quando essi sono effettivamente necessari per il prosieguo dell'applicazione. Ecco un esempio teorico: immaginiamo un'applicazione organizzata a tab. Il primo tab presenta una rubrica di contatti, il secondo tab un calendar per la gestione degli eventi e un terzo tab che funge da file system remoto. Ciascun tab presenta uno script JavaScript di inizializzazione (che per comodità chiameremo contact.js, calendar.js e filemanager.js). Come è facile intuire non è molto sensato obbligare il client a scaricare tutti i file all'inizializzazione dell'applicazione in quanto non è detto che l'utente corrente debba visualizzare tutti i tab disponibili. Con il Lazy Loading è possibile forzare il download dello script di inizializzazione di un determinato tab solamente nel momento in cui l'utente preme sul tab. Ovviamente un successivo click su un tab già precedentemente aperto, non farà scaricare nuovamente lo script, in quanto già scaricato precedentemente. Questa tecnica presenta quindi notevoli vantaggi: l'inizializzazione dell'applicazione è più rapida; il carico globale di banda è inferiore, in quanto viene scaricato solamente JavaScript necessario esiste la possibilità di creare JavaScript personalizzato che viene scaricato successivamente a fronte di particolari comportamenti dell'utente; è possibile bypassare la "same-domain policy" in quanto è possibile scaricare script da qualsiasi dominio, anche diverso da quello corrente. Versione originale: http://javascript.html.it/guide/lezione/4362/il-concetto-di-lazy-loading/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 1 di 1 13/06/2012 17:47 Due diversi approcci al Lazy Loading (versione per la stampa) | Guide ... 1 di 1 http://javascript.html.it/guide/stampa_lezione/4363/due-diversi-approc... Due diversi approcci al Lazy Loading La tecnica di Lazy Loading può essere attuata tramite due diversi approcci: Ajax Lazy Loading (ALL) e DOM Lazy Loading (DLL). Ajax Lazy Loading (ALL) Il primo approccio, che chiamiamo Ajax Lazy Loading (ALL) permette di scaricare un nuovo script JavaScript tramite una richiesta XmlHttpRequest e integra il codice grazie alla funzione eval() che valuta uno script a partire da una stringa. In questo modo qualsiasi porzione di codice non espressamente inserita all'interno di una funzione o di un oggetto sarà al termine del download dello script. Le richieste possono essere fatte sia in maniera sincrona, quindi bloccando il client in attesa del nuovo script (comodo dal punto di vista dello sviluppo ma pessimo come usabilità), sia in maniera asincrona (usabile ma spesso di difficile interpretazione e realizzazione per lo sviluppatore). Una possibile soluzione a questo secondo problema può essere quella di utilizzare una libreria di continuation trasformer che permette di effettuare richieste asincrone in maniera semplice e soprattutto senza perdersi tra decine di funzioni callback che sono fonte di preoccupazioni e di bug. Un'ottima libreria è Narrative JavaScript (http://www.neilmix.com/narrativejs/doc/) alla quale cercheremo di dedicare uno spazio più approfondito in seguito. DOM Lazy Loading Il secondo approccio, più orientato alla modifica del DOM della pagina (che quindi chiameremo DOM Lazy Loading, DLL) garantisce il download di un nuovo script inserendo un nuovo elemento <script> all'interno dell'elemento <head> della pagina, tramite la manipolazione del DOM. Il confronto Le peculiarità e le differenze tra questi approcci possono essere riassunte in un questo elenco: entrambi gli approcci permettono di eseguire codice JavaScript (non inserito in funzioni e oggetti) appena terminato il download e di definire funzioni per un utilizzo futuro tramite il DLL è possibile scaricare script anche da domini differenti rispetto a quello di origine (non possibile tramite l'utilizzo dell'oggetto XmlHttpRequest) tramite l'ALL è possibile essere più flessibili riguardo sia alla richiesta che alla risposta ricevuta, per esempio è possibile effettuare chiamate POST passando parametri complessi o ricevere script JavaScript all'interno per esempio di richieste SOAP o XML, mentre utilizzando il DLL è obbligatorio puntare verso una specifica risorsa JS la cache del browser viene attivata solamente tramite DLL, quindi in caso contrario è necessario un controllo esplicito per evitare doppie richieste Versione originale: http://javascript.html.it/guide/lezione/4363/due-diversi-approcci-al-lazy-loading/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:48 Analizziamo una libreria (versione per la stampa) | Guide JavaScript | ... http://javascript.html.it/guide/stampa_lezione/4364/analizziamo-una-li... Analizziamo una libreria Da poco pubblicata, Lazy Loader (http://ajaxian.com/archives/a-technique-for-lazy-script-loading) è una piccola libreria JavaScript, realizzata da Bob Matsuoka e rilasciata sotto licenza GPL che presenta una semplice ma funzionale implementazione della DLL. La libreria, che garantisce una notevole cross-browser compatibiliy, permette la definizione di callback che vengono scatenate una volta terminato il download e presenta una cache locale per evitare doppi ed inutili download di script. Lazy Loader si appoggia alla ormai diffusissima libreria Prototype (http://www.prototypejs.org/) e presenta un unico metodo statico che permette di scaricare appunto uno script remoto e di eventualmente assegnare una funzione di callback. Analizziamo il breve sorgente per capire meglio il funzionamento: var LazyLoader = {}; LazyLoader.timer = {}; LazyLoader.scripts = []; LazyLoader.load = function(url, callback) { try { if ($A(LazyLoader.scripts).indexOf(url) == -1) { LazyLoader.scripts.push(url); var script = document.createElement("script"); script.src = url; script.type = "text/javascript"; $$("head")[0].appendChild(script); if (callback) { script.onreadystatechange = function () { if (script.readyState == 'loaded' || script.readyState == 'complete') { callback(); } } script.onload = function () { callback(); return; } // caso particolare per Opera e Safari if ((Prototype.Browser.WebKit && !navigator.userAgent.match(/Version\/3/)) || Prototype.Browser.Opera) { LazyLoader.timer[url] = setInterval(function() { if (/loaded|complete/.test(document.readyState)) { clearInterval(LazyLoader.timer[url]); callback(); } }, 10); } } 1 di 2 13/06/2012 17:48 Analizziamo una libreria (versione per la stampa) | Guide JavaScript | ... http://javascript.html.it/guide/stampa_lezione/4364/analizziamo-una-li... } else { if (callback) { callback(); } } } catch (e) { alert(e); } } Dopo aver inizializzato il namespace (Lazy Loader) vengono definite due variabili statiche per la gestione della cache (LazyLoader.scripts) e per la gestione dei timer (LazyLoader.timer) necessari per garantire il corretto comportamento anche su Opera che non presenta API native JavaScript complete come i concorrenti Firefox, Internet Explorer e Safari. Successivamente viene definito il metodo statico principale della piccola libreria. Una volta verificato che lo script non sia gia stato caricato in precedenza (e quindi non esiste all'interno della cache locale) viene modificato il DOM della pagina creando un nuovo elemento <script> con i due attributi src e type e viene appeso all'elemento <head> proprio come descritto in precedenza nella definizione dell'approccio DLL. Inoltre, se esiste il parametro callback, esso viene assegnato all'onload dello script. Questa assegnazione presenta un meccanismo proprio per ciascun browser. In Internet Explorer è necessario utilizzare l'evento onreadystatechange controllando ogni volta lo stato (che deve essere 'complete' o 'loaded'), mentre in Firefox e Safari su utilizza l'evento onload. Nel caso di Opera (si noti lo sniffing sullo userAgent della richiesta tramite RegEx) è necessario creare un timeout che ogni 10 millisecondi controlla se davvero lo script è stato scaricato e in caso positivo esegue la callback definita dall'utente (e ovviamente interrompe il timeout con clearInterval()). Se lo script è già stato scaricato in precedenza, l'eventuale funzione callback viene invocata istantaneamente. Per ultimo viene stampata a video l'eventuale eccezione lanciata dalle richieste precedenti. Versione originale: http://javascript.html.it/guide/lezione/4364/analizziamo-una-libreria/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/notelegali.php 2 di 2 13/06/2012 17:48 Lazy Loading in azione (versione per la stampa) | Guide JavaScript | J... 1 di 4 http://javascript.html.it/guide/stampa_lezione/4365/lazy-loading-in-az... Lazy Loading in azione Per approfondire meglio questi concetti analizzeremo nel dettaglio una piccola applicazione di test che fa riferimento all'esempio teorico visto nella prima parte e utilizza la libreria LazyLoader. Come ho detto in precedenza la libreria dipende da Prototype e quindi è necessario includere anche questa dipendenza nel nostro progetto. Nonostante questo, non ho fatto uso di questa libreria (se non in un unico caso specifico, poi si capirà il perché) proprio per non creare confusione. L'architettura è molto semplice. L'applicazione presenta tre tab dedicati ognuno ad una specifica funzionalità: gestione dei contatti, calendar e filesystem remoto. Il comportamento di ciascuna di queste funzionalità è inserito in file specifici; avremo quindi contacts.js per la rubrica contatti, calendar.js per il calendario degli eventi e filesystem.js per il file manager remoto. Ecco comunque l'esempio (http://www.html.it/guide/esempi /jsavanzato/esempi/lazy_loading/index.html). Questi file ovviamente presentano lo stesso namespace e implementano tutti la stessa interfaccia per poter essere invocati dinamicamente. Oltre a questi file è presente un file (init.js) che inizializza l'applicazione in se e permette di scaricare in maniera lazy i file specifici per i tab e di invocare il loro metodo di inizializzazione. Iniziamo ad analizzare i file index.html e init.js. <html> <head> <script src="lib/prototype.js" type="text/javascript"></script> <script src="lib/lazyloader.js" type="text/javascript"></script> <script src="init.js" type="text/javascript"></script> <link rel="stylesheet" href="style.css"/> <script>window.onload = myApp.init;</script> <body></body> </html> Il file index.html è quasi banale: vengono caricate le dipendenze (Prototype e lazyloader), il file init.js che contiene l'inizializzazione della mini applicazione, il file CSS e viene associata la funzione myApp.init come callback dell'evento onload. myApp = {}; myApp.init = function() { var ul = document.createElement("UL"); ul.id = "tab"; var tabs = ["contact", "calendar", "filesystem"]; for(var i = 0; i<tabs.length; i++) { var li = document.createElement("LI"); li.innerHTML = tabs[i].toUpperCase(); li.id = "tab_"+tabs[i]; 13/06/2012 17:49 Lazy Loading in azione (versione per la stampa) | Guide JavaScript | J... 2 di 4 http://javascript.html.it/guide/stampa_lezione/4365/lazy-loading-in-az... li.onclick = myApp.changeTab.bind(this, tabs[i]); ul.appendChild(li); } document.body.appendChild(ul); var content = document.createElement("DIV"); content.id = "content"; document.body.appendChild(content); myApp.changeTab(tabs[0]); } myApp.changeTab = function(tab) { var tabEl = document.getElementById("tab_"+tab); tabEl.className = "selected"; if(myApp.currentTab) myApp.currentTab.className = ""; myApp.currentTab = tabEl ; var content = document.getElementById("content"); LazyLoader.load("tabs/"+tab+".js", function() { while(content.firstChild) content.removeChild(content.firstChild); myApp[tab].init(content); }); } Il file init.js, dopo aver inizializzato il namespace myApp, definisce due semplici funzioni. Il metodo init non fa nient'altro che definire dinamicamente la struttura della pagina creando una lista ciclando il vettore tabs e un div con id="content" che rappresenta il contenuto di ciascun tab. Da notare l'unico utilizzo di Prototype all'interno del mini esempio: l'utilizzo della funzione bind. Essa permette di modificare lo scope e i parametri passati ad una funzione proprio come avevamo visto nel precedente articolo dedicato proprio allo scope e all'utilizzo di metodi come callback di eventi. Bind permette di creare una nuova funzione che deriva da myApp.changeTab ma che viene eseguita avendo come parametro tabs[i] evitando quindi problemi derivanti dall'assegnazione di una funzione all'interno di un ciclo. Per ultimo viene invocato changeTab passando come parametro il nome del primo tab per inizializzarlo in automatico all'avvio. Il metodo changeTab (che risponde ad ogni click sui tab) si occupa di cambiare la classe CSS del li selezionato e invoca il metodo load della libreria LazyLoading passando come parametro il nome del file .js da scaricare ed una funzione di callback che verrà invocata al termine del download. Questa funzione inline, dopo aver rimosso qualsiasi elemento precedente contenuto nel div content, esegue il metodo init del tab attivato passando come parametro un riferimento all'elemento HTML da riempire con le informazioni relative. Guardando il codice capiamo subito che ciascun tab dovrà implementare il metodo myApp.nomeTab.init in quanto esso sarà invocato in maniera automatica una volta scaricato il file. Analizziamo ora la struttura di filesystem.js in quanto rappresenta il tab più complesso; gli altri sono di banale comprensione. myApp.filesystem = {}; 13/06/2012 17:49 Lazy Loading in azione (versione per la stampa) | Guide JavaScript | J... 3 di 4 http://javascript.html.it/guide/stampa_lezione/4365/lazy-loading-in-az... myApp.filesystem.data = [{ name:'Documenti', items: [{ name: 'Curriculum.odt' }, { name: 'Fatture.odt' }] }, { name:'Guide HTML', items: [{ name:'Tecniche Javascript', items: [{ name: 'Lo scope in JS' }, { name: 'Le closure' }] }] }] myApp.filesystem.init = function(content) { content.appendChild(myApp.filesystem.createUl(myApp.filesystem.data)); } myApp.filesystem.createUl = function(roots) { var ul = document.createElement("UL"); for(var i = 0; i<roots.length; i++) { var li = document.createElement("LI"); li.appendChild(document.createTextNode(roots[i].name)); if(roots[i].items) li.appendChild(myApp.filesystem.createUl(roots[i].items)); ul.appendChild(li); } return ul; } Innanzitutto è necessario inizializzare il namespace relativo al tab (myApp.filesystem) e definire alcuni dati di test da mostrare (myApp.filesystem.data). Successivamente viene implementato il metodo init (che ricordo essere obbligatorio per ciascun tab) il quale al suo interno non fa nient'altro che invocare un altro metodo (myApp.filesystem.createUl) ed appendere il suo risultato al div ricevuto come parametro da init.js. Il metodo createUl è un metodo ricorsivo che si occupa di costruire una struttura ad albero a partire da un vettore di nodi. Nel caso infatti che un nodo abbia dei nodi figli (nel nostro caso si utilizza la proprietà items) viene invocata ricorsivamente la stessa funzione. In questo modo il metodo torna l'oggetto UL formattato. Conclusioni Dalle lezioni abbiamo sicuramente apprezzato il Lazy Loading come una tecnica per migliorare nettamente le performance di una applicazione web moderna. Utilizzando questa tecnica sussiste però l'esigenza di creare una struttura comune alle varie funzionalità (nel nostro piccolo esempio il file init.js) che permetta non solo di scaricare i nuovi file "a comando" ma anche di garantire quella dinamicità che le applicazioni web necessitano. Una struttura di questo tipo non è sicuramente di facile realizzazione forse più dal punto di vista progetturale che dello sviluppo. Come al solito l'esempio, pur banale che sia, è riuscito a cogliere i vantaggi e le criticità di questo approccio in maniera semplice e di facile comprensione.† Per ultimo alleghiamo i sorgenti dell'applicazione (http://www.html.it/guide/esempi/jsavanzato/esempi/lazyloading.zip). 13/06/2012 17:49 Lazy Loading in azione (versione per la stampa) | Guide JavaScript | J... 4 di 4 http://javascript.html.it/guide/stampa_lezione/4365/lazy-loading-in-az... Versione originale: http://javascript.html.it/guide/lezione/4365/lazy-loading-in-azione/ © 1997-2006 HTML.it La vendita, il noleggio, il prestito e la diffusione del contenuto di questa pagina sono vietate, tranne nei casi specificati nella pagina http://www.html.it/info/note-legali.php 13/06/2012 17:49