![]() |
Komfortable Timer-Funktion |
|
| |
| Homepage-URL: |
|---|
| E-Mail: |
|---|
Das Script wurde im Zusammenhang mit einer
Forumsdiskussion von Struppi entwickelt und von Mathias Sch�fer dokumentiert.
Bei Fragen zu diesem Beitrag bitte den Autor des Beitrags kontaktieren!
JavaScript-Programme sind nicht mit anderen Computerprogrammen zu vergleichen, die eine bestimmten Startzeit und Endzeit ihrer Ausf�hrung haben. JavaScripte werden immer wieder f�r kurze Zeit aktiv, etwa wenn auf Ereignisse reagiert wird. Wenn ein JavaScript tats�chlich mehrere Sekunden an einem St�ck l�uft und die gesamten Rechenkapazit�ten in Beschlag nimmt, friert der Browser ein oder zeigt einen Dialog, mit dem das Script abgebrochen werden kann.
Dementsprechend gibt es in JavaScript keinen Befehl sleep, der einfach f�r eine gegebene Zeit nichts tut. So etwas ist in JavaScript nicht m�glich. Allerdings lassen sich �ber
window.setTimeout und
window.setInterval Funktionen nach einer angegebenen Wartezeit aufrufen.
Soll ein Script �ber l�ngere Zeit laufen und zwischen einzelnen Befehlen eine Wartezeit verstreichen lassen, ist dies in JavaScript nur mit window.setTimeout �ber das �Stop & Go�-Verfahren m�glich. Auf diese Weise k�nnen z.B. Animationen umgesetzt werden.
Ein verz�gerter Aufruf sieht in manch anderen Sprachen schematisch so aus:
befehl1; sleep(5); befehl2;
In JavaScript hingegen notieren wir eine Funktion, die wir zeitverz�gert aufrufen:
befehl1;
function befehl2 () {}
window.setTimeout(befehl2, 5000);
Knifflig wird es nun, wenn ein Befehl mit sich �ndernden Parametern mehrmals ausgef�hrt werden soll und dazwischen jeweils eine Zeitspanne liegen soll. Ein Countdown etwa lie�e sich in anderen Sprachen schematisch so realisieren:
for ($i = 10; $i > 0; i--) {
echo $i . "\n";
sleep(1);
}
In JavaScript wird dies �blicherweise mit einer Funktion gel�st, die sich immer wieder selbst verz�gert aufruft und dabei den Schleifenz�hler �bergibt:
functin countdown (i) {
if (i == undefined) {
// Startwert
i = 10;
}
alert(i);
if (i > 0) {
i--;
// Funktion verz�gert aufrufen
var timeout = window.setTimeout("countdown(" + i + ")", 1000);
}
}
// Countdown ansto�en
countdown();
Wir sehen hier bereits, wie kompliziert die Umsetzung eines einfachen Countdowns wird. Diese verbreitete Umsetzung l�sst sich zun�chst durch den Einsatz von
Closures vereinfachen. Damit l�sst sich die �bergabe von Parametern einfacher gestalten: Anstatt window.setTimeout("countdown(" + i + ")", 1000); k�nnen wir ebenso eine anonyme Funktion notieren, die als Closure wirkt und in der die lokale Variable i zur Verf�gung steht: window.setTimeout(function () { countdown(i) }, 1000);. Dies ist vor allem sinnvoll, wenn mehr und komplexere Variablen �bergeben werden m�ssen, die nicht so einfach in einen String linearisiert werden k�nnen.
Wenn mehrere Funktionen periodisch aufgerufen werden sollen, so ist es umst�ndlich, denselben Mechanismus immer wieder neu zu notieren. Im Folgenden wird eine Helferfunktion vorgestellt, mit der sich solche Aufgaben komfortabel umsetzen lassen.
Function.prototype.Timer = function (interval, calls, onend) {
var count = 0;
var payloadFunction = this;
var startTime = new Date();
var callbackFunction = function () {
return payloadFunction(startTime, count);
};
var endFunction = function () {
if (onend) {
onend(startTime, count, calls);
}
};
var timerFunction = function () {
count++;
if (count < calls && callbackFunction() != false) {
window.setTimeout(timerFunction, interval);
} else {
endFunction();
}
};
timerFunction();
};
Zum Verst�ndnis der Funktionsweise ist es notwendig, sich vor Augen zu f�hren, dass Funktionen in JavaScript nichts anderes als Objekte sind, die sich im Grunde genauso wie andere Variablen verhalten k�nnen. Au�erdem werden verschachtelte Funktionen und Closures an mehreren Stellen verwendet, um Variablen zwischen verschiedenen Funktionen zu teilen, ohne dass sie global verf�gbar sind.
Die Helferfunktion wird an das prototype-Objekt des Function-Objektes geheftet. Dies nennt sich prototypische Erweiterung und hat den Effekt, dass durch die Zuweisung Function.prototype.methode = function () {}; alle Funktionen fortan �ber die notierte Methode als gew�hnliches Unterobjekt verf�gen. Denn Funktionen stammen vom Function-Konstruktor ab und erben dessen Methoden. Ein einfacheres Beispiel der prototypischen Erweiterung:
Function.prototype.zeigeParameterzahl = function () {
alert("Diese Funktion erwartet " + this.length + " Parameter.");
};
function irgendeineFunktion (parameter1, parameter2, parameter3) {}
irgendeineFunktion.zeigeParameterzahl();
Die gezeigte Timer-Methode erwartet drei Parameter:
| Parameter | Bedeutung |
|---|---|
interval |
Zeit zwischen den Aufrufen in Millisekunden |
calls |
Anzahl der Aufrufe |
onend |
Funktion, die am Ende der Wiederholungen gestartet werden soll (optional) |
Am Anfang der Funktion werden einige Variablen initialisiert:
// Z�hler, wie oft die Funktion schon ausgef�hrt wurde, beginne bei 0. var count = 0; // Lege eine Referenz auf die Funktion an, die mehrfach ausgef�hrt werden soll. // Dies ist n�tig, damit in den folgenden notieren verschachtelten Funktionen // ein Zugriff darauf m�glich ist. var payloadFunction = this; // Speichere den Startzeitpunkt der Aufrufkette. var startTime = new Date();
Der Rest besteht aus drei Funktionen, die als lokale Variablen gespeichert werden. Alle wirken als Closures, das hei�t, sie werden nach dem Ende der Ausf�hrung der Timer-Funktion nicht gel�scht, sondern haben weiterhin Zugriff auf die eben initialisierten Variablen. Zudem haben die drei Funktionen aus demselben Grund Zugriff aufeinander, sie liegen im selben abgeschlossenen Scope (Geltungsbereich).
var callbackFunction = function () {
return payloadFunction(startTime, count);
};
Die callbackFunktion wird aufgerufen, w�hrend der Timer l�uft. Sie startet die eigentliche Funktion, die mehrfach ausgef�hrt werden soll (payloadFunction). Ihr �bergibt sie den Startzeitpunkt sowie die fortlaufende Nummer des wiederholten Aufrufes. Wenn die callbackFunction das Ergebnis false zur�ckgibt, wird der Timer beendet. Das hei�t, dass in der zu wiederholenden Funktion einfach return false notiert werden kann, um den Timer zu beenden - callbackFunction gibt diesen R�ckgabewert durch.
var endFunction = function () {
if (onend) {
onend(startTime, count, calls);
}
};
endFunction wird am Ende aller Wiederholungen aufgerufen. Sie kapselt die als Parameter entgegen genommene onend-Funktion und �bergibt ihr den Startzeitpunkt, die tats�chliche Anzahl der Wiederholungen sowie die urspr�nglich �bergebene Anzahl der Wiederholungen.
var timerFunction = function () {
count++;
var callbackReturnValue = callbackFunction();
if (count < calls && callbackReturnValue != false) {
window.setTimeout(timerFunction, interval);
} else {
endFunction();
}
};
Die Timer-Funktion schlie�lich erh�ht den Z�hler, f�hrt die callbackFunction sowie sich selbst aus, wenn das Limit an Aufrufen noch nicht erreicht ist und die callbackFunction nicht false ergeben hat. Im Falle eines Abbruchs oder bei Erreichen des Limits wird die endFunction ausgef�hrt.
Am Ende der Timer-Methode wird schlie�lich die timerFunction erstmalig angesto�en, der weitere Ablauf wird an sie �bergeben.
Ein kompleres Beispiel wird verdeutlichen, wie die vorgestellte Helferfunktion angewendet wird, wie sie abl�uft und wieso sie derartig strukturiert ist.
Die beispielhafte Aufgabe ist ein einfacher Countdown, der in einem Element ausgegeben werden soll. Gleichzeitig sollen zwei dieser Countdowns laufen.
Anzeigebeispiel: So sieht's aus
function leadingzero (number) {
return (number < 10) ? '0' + number : number;
}
function countdown (seconds, target) {
var element = document.getElementById(target);
var calculateAndShow = function () {
if (seconds >= 0) {
var h = Math.floor(seconds / 3600);
var m = Math.floor((seconds % 3600) / 60);
var s = seconds % 60;
element.innerHTML=
leadingzero(h) + ':' +
leadingzero(m) + ':' +
leadingzero(s);
seconds--;
} else {
return false;
}
};
var completed = function () {
element.innerHTML = "<strong>Liftoff!<\/strong>";
};
calculateAndShow.Timer(1000, Infinity, completed);
}
window.onload = function () {
new countdown(10, 'counter1');
new countdown(5, 'counter2');
};
<p id="counter1"></p> <p id="counter2"></p>
Herzst�ck ist die Funktion countdown, die als Konstruktor eines eigenen Objektes dient (wobei diesem Objekt keine �ffentlichen Eigenschaften zugewiesen werden). Beim Laden der Seite werden mit new zwei Countdown-Objekt mit verschiedenen Parametern erzeugt:
window.onload = function () {
new countdown(10, 'counter1');
new countdown(5, 'counter2');
};
Die countdown-Konstruktorfunktion erwartet zwei Parameter: seconds ist die Sekundenzahl, von der aus heruntergez�hlt werden soll, und target ist ein String mit der ID desjenigen Elements, in dem der Countdown angezeigt werden soll. In diesem Fall werden zwei Countdowns gestartet mit den Element-IDs counter1 und counter2, einer mit zehn und einer mit f�nf Sekunden Laufzeit.
Zun�chst wird eine Referenz namens element auf das Element angelegt, in dem der Countdown ausgegeben werden soll:
var element = document.getElementById(target);
Im Konstruktor wird nun eine verschachtelte Funktion namens calculateAndShow notiert. Dies ist die eigentliche Funktion, die mehrfach aufgerufen werden soll. Durch den Closure-Effekt sind in ihr die lokalen Variablen von countdown �ber die verschiedenen Aufrufe hinweg verf�gbar - dies wird zum Zugriff auf seconds und element ben�tigt.
calculateAndShow fragt ab, ob seconds noch nicht bei 0 angekommen ist. Wenn nicht, rechnet es den Sekundenwert in das Format HH:MM:SS (Stunden, Minuten, Sekunden) um und schreibt diesen Wert ins Element:
var h = Math.floor(seconds / 3600);
var m = Math.floor((seconds % 3600) / 60);
var s = seconds % 60;
element.innerHTML=
leadingzero(h) + ':' +
leadingzero(m) + ':' +
leadingzero(s);
Schlie�lich wird mit seconds--; der Variablenwert verringert. Wenn dieser beim n�chsten Aufruf von calculateAndShow bei 0 angekommen wird, wird false zur�ckgegeben. Dies f�hrt, wie oben beschrieben wurde, dazu, dass die Kette an Aufrufen abbricht.
Am Ende der Funktion countdown wird die Methode Timer des calculateAndShow-Funktionsobjektes gestartet. Die Funktion hat die Methode durch die prototypische Erweiterung erhalten.
calculateAndShow.Timer(1000, Infinity, completed);
Als Parameter �bergeben wir 1000 Millisekunden als Intervall-L�nge, die vordefinierte Konstante Infinity (unendlich) als Anzahl der Wiederholungen sowie die verschachtelte Funktion completed, die nach den wiederholten Aufrufen gestartet wird. Nat�rlich soll calculateAndShow nicht unendlich oft ausgef�hrt werden, wir �bergeben ihr aber selbst die Kontrolle dar�ber, wann die Kette an Wiederholungen abgebrochen werden soll - in dem Fall gibt sie einfach false zur�ck.
Folgendes Ablaufprotokoll zeigt die Funktionsweise der Countdowns im Beispiel:
countdown
Timer
timerFunction
callbackFunction
calculateAndShowTimeouttimerFunction-Aufrufe wiederholen sich bis zum letzten Aufruf. Dieser startet keinen neuen Timeout, sondern die endFunction:)timerFunction
callbackFunction
calculateAndShowendFunction
completedHier zeigt sich, wie timerFunction den Ablauf regelt und die Kontrolle beh�lt, wenn sie einmal angesto�en wurde. Sie f�hrt mithilfe von callbackFunction die eigentliche Wiederholungsfunktion calculateAndShow aus und die Wiederholungen werden mit endFunction beendet.
Die vorgestellte Funktion soll beispielhaft zeigen, wie fortgeschrittene Programmiertechniken genutzt werden k�nnen, um einen einfach benutzbaren Timer zu realisieren. Dazu geh�ren prototypische Erweiterung, objektorientierte Programmierung, Funktionen als Objekte sowie Closures.
Das Konzept hat selbstverst�ndlich Vor- und Nachteile und ist ausbauf�hig. Andere Umsetzungen wie etwa der
periodicalExecuter aus dem JavaScript-Framework Prototype besitzen eine stop-Methode, mit der die Kette der Aufrufe auch �von au�en� unterbrochen werden kann, anstatt nur durch die mehrfach aufgerufene Funktion selbst.
Es gibt zudem verschiedene Methoden, um Variablen von einer Ausf�hrung zur anderen zu konservieren, ohne dass sie notwendigerweise im globalen Scope liegen m�ssen. Das Anwendungsbeispiel nutzt hier ein eigenes Objekt mit privaten Eigenschaften, die durch den Closure-Effekt in der wiederholten Funktion verf�gbar sind, die ihrerseits eine private Methode ist.
© 2007
Impressum, f�r diese Seite:
zapperlott@gmail.com