Rolf B: Best Practice zur Reinitialisierung von Variablen

Beitrag lesen

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