Best Practice zur Reinitialisierung von Variablen
bearbeitet von Rolf BHallo 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:
~~~js
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 vier: `timeoutId`, `startIt`, `stopIt` und `doItEverySecond`. Ja, richtig gelesen, die Funktionen sind lokale Daten von `runIt`. 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