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&lt;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&lt;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