freemind Supervisor sezione Programmazione


Registrato: 04/04/07 21:28 Messaggi: 4643 Residenza: Internet
|
Inviato: 12 Apr 2008 13:59 Oggetto: JavaScript e timing |
|
|
Salve forum.
Di solito scrivo poco e normalmente i miei post sono in risposta a qualcuno.
Oggi scrivo qui perchè in questi giorni mi sono imbattuto su un simpatico problema: le funzioni di timing di javascript applicate ai metodi di una classe da me creata. La soluzione credo possa interessare molti.
Tutti sanno come si gestiscono i timer in js: ad esempio setTimeout("funz()",millisec) fa sì che la funzione funz() venga chiamata dopo millisec millisecondi e nel frattempo l'esecuzione del codice prosegue alla riga successiva. Orbene (che parolone), se proviamo a chiamare setTimeout(...) dentro ad una classe per farle eseguire un metodo, tutto va a ramengo!
Lo stesso discorso vale per setInterval(...) e tutte le funzioni di timing.
Ho trovato per la rete (w internet!) il modo di risolvere il problema.
Ora mostrerò come scrivere una semplice classe Countdown che implementaerà un countdown in secondi.
Il codice (dovrebbe) funzionare sia con ie che con gli altri browser (per ie vedremo che 2 palle dovremo farci).
Prima di tutto dobbiamo porci la domanda: "Perchè non funziona un tubo se uso setTimeout(...) al solito modo?".
La risposta è: "Il problema è che non riusciamo a chiamare window.setTimeout nel contesto della nostra classe per cui this è fuori scope".
Lo script non funziona (neppure ci provo ad eseguirlo, tanto non va):
Codice: |
// classe Countdown
this.Countdown=function(sec,targetDiv)
{
// div che conterrà il timer
this.tdiv = document.getElementById(targetDiv);
// secondi di durata del conteggio
this.sec = sec
// metodo pubblico per eseguire il countdown
this.displayCountdown=makeCountdown;
}
// questo metodo tenta di implementare il timer
function makeCountdown()
{
if (this.sec==0)
this.tdiv.innerHTML="fine";
else
this.tdiv.innerHTML=this.sec;
--this.sec;
if (this.sec>-1)
setTimeout("displayCountdown()",1000); // (*), non va!
}
|
Se da qualche parte provate ad usare 'sto coso avrete degli errori sulla chiamata (*)
Dobbiamo fare in modo di eseguire la chimata nel contesto del nostro oggetto.
Con firefox possiamo utilizzare la versione che segue:
Codice: |
// classe Countdown
this.Countdown=function(sec,targetDiv)
{
// div che conterrà il timer
this.tdiv = document.getElementById(targetDiv);
// secondi di durata del conteggio
this.sec = sec
// metodo pubblico per eseguire il countdown
this.displayCountdown=makeCountdown;
}
function makeCountdown()
{
if (this.sec==0)
this.tdiv.innerHTML="fine";
else
this.tdiv.innerHTML=this.sec;
--this.sec;
if (this.sec>-1)
setTimeout(function(thisObj) {thisObj.displayCountdown(); },1000,this); // (**)
}
|
Bene bene, resta il problema ie.
Purtroppo le funzioni di timing di ie non supportano l'ultimo parametro passato a (**) per cui non possiamo referenziare la nostra istanza.
Dobbiamo creare un vettore globale che conterrà i riferimenti alle nostre istanze e utilizzare una funzione esterna per scegliere quale istanza andare a prendere per la chiamata.
Il codice che segue implementa la classe in modo che funzioni su ogni browser (almeno i più noti: ff,safari,opera,ie,konqueror)
Codice: |
<script type="text/javascript">
// var globali per gestire le chiamate per ie
var globalScope_Counter = new Array(); // vettore delle istanze
var globalIndex_Counter = 0; // indice del precedente
this.Countdown=function(sec,targetDiv)
{
/*
Dobbiamo creare un id univoco dell'istanza
e caricare questa nel vettore
*/
this.uniqueId = 'Obj' + globalIndex_Counter++;
globalScope_Counter[this.uniqueId]=this;
// div che conterrà il timer
this.tdiv = document.getElementById(targetDiv);
// secondi di durata del conteggio
this.sec = sec
// metodo pubblico per eseguire il countdown
this.displayCountdown=makeCountdown;
}
function makeCountdown()
{
if (this.sec==0)
this.tdiv.innerHTML="fine";
else
this.tdiv.innerHTML=this.sec;
--this.sec;
if (this.sec>-1)
{
// ie
if (document.all) // (***)
{
setTimeout( 'ieIntervalHandler("' + this.uniqueId + '","displayCountdown")', 1000 );
}
else
{
setTimeout(function(thisObj) {thisObj.displayCountdown(); },1000,this); // (**)
}
}
}
// gestore delle chiamate per ie
function ieIntervalHandler( id, strFunc )
{
/* Scope corretto per la chiamata */
var scope = globalScope_Counter[id];
eval( "scope." + strFunc + "()" );
}
</script>
|
La condizione (***) verifica se stiamo usando ie o altro.
Nel primo caso applica setTimeout(...) alla funzione ieIntervalHandler(...) che vuole come parametri l'id dell'istanza e il metodo di questa che vogliamo chiamare dopo 1000 (in questo caso) millisecondi.
Il tutto si gioca tramite la funzione eval(...) che data la stringa passata genera del codice javascript.
Qui c'è un esempio di utilizzo di eval (così chi ha bisogno può chiarirsi le idee).
link
Per usare lo script possiamo scrivere qualcosa del genere (non mi soffermo sulla bellezza del codice html così come non ho pignolato sul controllo degli errori delle funzioni js)
Codice: |
<html>
<head>
<script type="text/javascript">
// copiate qui l'ultma versione dello script
</script>
</head>
<body>
<div id="timer1"></div>
<div id="timer2"></div>
<script type="text/javascript">
var c1=new Countdown(5,"timer1");
c1.displayCountdown();
var c2=new Countdown(8,"timer2");
c2.displayCountdown();
</script>
</body>
</html>
|
L'ultima versione può causare dei memoryleak dovuti al sovraccarico del vettore globale nel caso utilizzassimo più contatori nella pagina. Infatti dopo la chiamata
Codice: | var c=new Countdown(5,"timer"); |
le prime righe del costruttore salvano a priori l'oggetto nel vettore indipendentemente che si tratti di ie o ff. Si potrebbe tentare di salvare nel vettore solo se il browser è ie modificando il costruttore nel modo che segue:
Codice: |
this.Countdown=function(sec,targetDiv)
{
/*
Dobbiamo creare un id univoco dell'istanza
e caricare questa nel vettore
*/
if (document.all)
{
this.uniqueId = 'Obj' + globalIndex_Counter++;
globalScope_Counter[this.uniqueId]=this;
}
// div che conterrà il timer
this.tdiv = document.getElementById(targetDiv);
// secondi di durata del conteggio
this.sec = sec
// metodo pubblico per eseguire il countdown
this.displayCountdown=makeCountdown;
}
...
...
|
La variante funziona anche se non ho fatto test per verificare il problema dei memoryleak ma a fiuto vettore più piccolo implica meno memoria occupata e quindi meno probabilità di memoryleak dovuti a deallocazioni fallite.
Spero che tutta questa cosa (lunghissima) possa aiutare chi,come me, si è trovato davanti a questo problema che all'inizio mi sembrava non avere senso.
Salve a tutti |
|