Hallo Christian,
das scheint aber unter gewissen Voraussetzungen unabdingbar (ruft z.B. Funktion A eine rekursive Timout Funktion auf, welche in Funktion B wieder eingefangen werden muss [clearTimeout()], so muss das Timeout selbst in einer für beide Funktionen zugänglichen Variable gespeichert werden
In den allermeisten Fällen scheint das aber nur so. Und gerade in deinem Beispielfall ist es nicht so.
JavaScript bietet mit Closures einige Möglichkeiten, Daten zwischen Funktionen zu teilen, ohne globale Variablen zu benötigen. Dafür verwendet man an Stelle globaler Variablen einen Funktionskontext. Mal ganz strikt vereinfacht:
function runIt(startButton, stopButton) {
let timeoutId;
function startIt() {
startButton.removeEventListener("click", startIt);
timeoutId = setTimeout(doItEverySecond, 1000);
}
function stopIt() {
clearTimeout(timeoutId);
stopButton.removeEventListener("click", stopIt);
}
function doItEverySecond() {
// do something
timeoutId = setTimeout(doItEverySecond, 1000);
}
startButton.addEventListener("click", startIt);
stopButton.addEventListener("click", stopIt);
}
runIt(btn1, btn2);
Wenn runIt aufgerufen wird, entsteht eine Closure. Diese enthält alle lokalen Variablen der Funktion mit ihren Werten. Im Beispiel sind das sechs: startButton
, stopButton
, timeoutId
, startIt
, stopIt
und doItEverySecond
. Ja, richtig gelesen, die Funktionen sind lokale Daten im runIt
Aufruf. Allerdings read-only. Ein wichtiger Aspekt an diesen Funktionsobjekten ist, dass sie eine Referenz auf die Closure enthalten, in der sie entstanden sind.
Die runIt
-Funktion, die ich skizziert habe, bekommt zwei Button-Objekte übergeben und registriert für beide einen click-Handler. Und dann endet sie. Aber weil startIt
und stopIt
nun als Eventhandler registriert sind, existieren noch Referenzen auf sie. Und weil startIt
und stopIt
Referenzen auf die runIt-Closure halten, existiert auch die Closure weiter. Und damit auch timeoutId
und doItEverySecond
.
Um keinen mehrfachen Start zu produzieren, deregistriert sich die startIt
Funktion selbst, sobald man start geklickt hat. Ssst, ein Faden weniger, an dem die Closure hängt.
D.h. ohne ein einzige globale Variable hängt diese Closure nun an einem einzigen Faden im DOM und tickt vor sich hin. Immer wenn setTimeout
gerufen wird, wird doItEverySecond
als Timeout-Handler registriert, das ist der zweite Faden, an dem die Closure hängt. Bis zu dem Moment, wo man Stop klickt. Das löscht den Timeout - schnipp - und deregistriert stopIt
als click-Handler - schnapp. Beide Fäden sind weg, und bei nächster Gelegenheit wird die Closure vom Garbage Collector abgeräumt.
Und wenn Du runIt
aufrufst, geht es wieder von vorne los.
Ein besonders schicker Aspekt der Sache ist: All das ist komplett eingekapselt. Keine globale Variable. Absolut keine Möglichkeit, dass sich irgendwelche Dinge gegenseitig überlagern. Du könntest sogar zwei Start- und zwei Stop-Buttons haben. Du rufst runIt zweimal auf, einmal für das eine Start/Stop Pärchen, und noch einmal für das zweite. Und nun ticken zwei Timeouthandler vor sich hin, keiner weiß was vom anderen, keiner hat auch nur die geringste Chance, irgendwie den anderen zu beeinflussen.
Ich kann es nur so abstrakt erklären, weil Du ja auch nur abstrakt dein Problem vorgestellt hast. Für requestAnimationFrame
gilt die Argumentation übrigens analog.
Wenn Du damit nicht weiterkommst, erzähl mehr über deine Problemstellung. Oder gib uns einen Link auf die Seite, falls sie öffentlich ist, dann können wir uns deinen Code anschauen.
Rolf
sumpsi - posui - obstruxi