Roadster: Anfängerfragen zum Thema Funktionen in JS

Hallo allerseits,

ich habe mal eine Frage zum Thema Funktionen in JavaScript, denn irgendwie ist mir das in den Dokumentationen nicht hundertprozentig klar geworden:

Es ist doch grundsätzlich so, dass eine Funktion, die man ins script schreibt, ersteinmal nur wie ein Buch in einem Bücherregal abgelegt ist (ok, vielleicht nicht der gelungenste Vergleich).

Das heißt, wenn ich beispielsweise irgendwo in mein script schreibe

function closeinfoscreen() {var infoscreen = document.getElementById("infoscreen");  infoscreen.style.display = "none";};

dann passiert ersteinmal garnichts, denn die Funktion muss erst "aufgerufen" werden, wie man ein Buch aus dem Regal nimmt und darin blättert, indem man beispielsweise schreibt

document.getElementById("infoscreen").addEventListener("click", closeinfoscreen);

Die Frage ist jetzt:

Was passiert, wenn die Funktion ausgeführt ist, das heißt, wenn ich das Buch gelesen habe?

Müsste ich das Buch dann nicht wieder zurück ins Regal stellen, sprich die Funktion per Befehl für beendet erklären, damit sie nicht im Speicher bleibt und dessen Kapazität beansprucht?

Oder passiert das automatisch?

Oder hängt es vom Inhalt der Funktion ab?

Eine Funktion kann ja so beschrieben sein, dass sie in einer Schleife potentiell ewig weiterläuft, oder, wie im Fall meines Code-Beispiels, lediglich die Anweisung beinhalten, nur eine bestimmte Aktion auszuführen, deren Erfolg mit true oder false zu verifizieren wäre, die also ein klar abgrenzbares Ende besitzt.

Wenn ich also über das DOM die einzelnen Elemente meiner Seite steuere, indem ich Funktionen nach dem Schema meines Code-Beispiels verwende, muss ich die dann irgendwie wieder "schließen", oder werden solche Funktionen nach vollbrachter Tat automatisch wieder "zurück ins Regal gestellt", und verbrauchen keinen Speicher mehr?

Überhaupt, wie verhält sich das mit "inaktiven" Funktionen - welchen Einfluss übt deren schieres Vorhandensein im script auf die Performance einer Seite aus?

Da meine js-Seitensteuerung ziemlich umfangreich zu werden droht, mache ich mir so meine Gedanken über eine möglichst speicherschonende Programmierung...

(Variablen nur innerhalb von Funktionen; EventListener, wenn Element nicht mehr angezeigt wird wieder abschalten usw...)

Würde mich wie immer über etwas Erleuchtung freuen!

Gruß,

euer

Roadster.

  1. Noch ein kleiner Nachtrag:

    Ich habe in meinem script gewissermaßen eine Hierarchie eingeführt, das heißt, ich habe meine Funktionen für die Steuerung der Seitenelemente nach folgendem Schema aufgebaut:

    function basisFunktion1() {var element1 = document.getElementById("element1");  var element2 = document.getElementById("element2");  element1.eigenschaft = "wert";  element2.eigenschaft = "wert";}; function basisFunktion2() {var element3 = document.getElementById("element3");  var element4 = document.getElementById("element4");  element3.eigenschaft = "wert";  element4.eigenschaft = "wert";}; function höhereFunktion() {if(bedingung1) {basisFunktion1();}  else  if(bedingung2) {basisFunktion2();}}; // ...und so weiter und so fort

    Das heißt, ich habe ersteinmal eine Bibliothek an "simplen" Funktionen angelegt, auf die ich dann bei den "komplexeren" Funktionen direkt zurückgreife.

    Frage mich aber, ob es auf diese Art speicherschonender ist oder ob es in dieser Hinsicht nicht doch besser wäre, alles in einer Funktion zusammenzufassen:

    function höhereFunktion() {var element1 = document.getElementById("element1");  var element2 = document.getElementById("element2");  var element3 = document.getElementById("element3");  var element4 = document.getElementById("element4");  if(bedingung1) {element1.eigenschaft = "wert";                  element2.eigenschaft = "wert";}  else  if(bedingung2) {element3.eigenschaft = "wert";                  element4.eigenschaft = "wert";}};

    Gruß,

    Roadster.

  2. Meine Damen und Herren, habe ich Ihre Aufmerksamkeit?

    Die Frage ist jetzt:

    Was passiert, wenn die Funktion ausgeführt ist, das heißt, wenn ich das Buch gelesen habe?

    Das ist schon keine Anfägerfrage mehr.
    Ein laufendes JavaScript-Programm hat einen sogenannten Event-Loop. Der besteht vorallem aus zwei Verwaltungs-Strukturen: die asynchrone Event-Queue und der Callstack.

    Jeder Funktionsaufruf sorgt dafür, dass unittelbar ein neues Element auf den Callstack gelegt wird. In diesem Element werden allerlei Meta-Informationen über den Funktionsaufruf abelegt, zum Beispiel lokale Variablen, Parameter und ein Programm-Zähler, damit der Interpreter immer genau weiß, welche Anweisung er gerade ausführt. Wenn die Funktion abgearbeitet ist, dann wird das Element wieder vom Callstack gelöscht.

    Wenn ein Funktionsaufruf intern einen eine weitere Funktion aufruft, wird für diesen inneren Funktionsaufruf sofort ein weiteres Element auf den Callstack gelegt usw. usw. Irgendwann werden diese verschachtelten Funktionsaufrufe der Reihe nach wieder terminieren und der Stapel leert sich allmählich wieder.

    Wenn zwischenzeitlich ein asynchrones Ereignis eintritt, zum Beispiel ein Klick auf einen Button, oder eine Antwort auf ein AJAX-Request, dann unterbricht der Browser nicht die gegenwärtig ausgeführten Funktionen und er legt auch kein neues Element auf den Callstack, er arbeitet den aktuellen Callstack ganz gewöhnlich und ohne Unterbrechung ab. Der Browser vergisst den Klick aber auch nicht, stattdessen reiht er einen Auftrag in die Event-Queue ein. Und wenn der Callstack irgendwann mal wieder leer ist, schaut der Browser in der Event-Queue nach, nimmt sich dort den nächsten Einträg und arbeitet wieder den ganzen Stapel ab.

    Ein Beispiel:

    function a () { b(); } function b () { c(); } function c () { alert('whoop'); } a();

    Das Programm ruft erst die Funktion a auf, diese ruft intern b auf, diese wiederum ruft c auf und c ruft schlussendlich alert('whoop'); auf. alert('whoop') ist irgendwann fertig und gibt die Kontrolle an c zurück. c gibt die Kontrolle später zurück an b, b gibt sie zurück an a und irgendwann sagt auch a ich bin fertig.

    Wenn wir dieser sprachlichen Ausführung folgen, dann sieht der Callstack dabei ungefähr so aus:

    betrete a()

    +---+ | a | +---+

    betrete b()

    +---+ | b | +---+ | a | +---+

    betrete c()

    +---+ | c | +---+ | b | +---+ | a | +---+

    betrete alert('whoop')

    +-------+ | alert | +-------+ | c | +---+ | b | +---+ | a | +---+

    verlasse alert('whoop')

    +---+ | c | +---+ | b | +---+ | a | +---+

    verlasse c()

    +---+ | b | +---+ | a | +---+

    verlasse b()

    +---+ | a | +---+

    verlasse a()
    leerer Stapel

    Wichtig an dieser Stelle ist, dass die Elemente oben auf dem Callstack keinen Zugriff auf die Variablen in den Funktionen weiter unten im Callstack haben. Dies gilt auch andersherum.

    Im nächsten Beispiel schauen wir uns mal ein Programm an, dass auch die Event-Queue benutzt. Dazu brauchen wir als erstes einen asynchronen Funktionsaufruf. Wir könnten uns einen Klick vorstellen, aber wir bedienen uns der einfachsten aller Möglichkeiten: setImmediate(). Die Funktion nimmt eine Callback-Funktion als Parameter entgegen und macht nichts anderes als diese "asynchron" aufzurufen. Das heißt, die Callback-Funktion wird nicht sofort auf den Callstack gelegt, sondern in die Event Queue eingereiht:

    function a() {    setImmediate( b ); } function b() {    alert( 'whoop' ); } a();

    Wir schauen uns wie vorher den Callstack schrittweise an:

    betrete a():
    Callstack:

    +---+ | a | +---+

    Event-Queue: leer

    betrete setImmediate():
    Callstack:

    +--------------+ | setImmediate | +---+----------+ | a | +---+

    Event-Queue: b

    verlasse setImmediate():
    Callstack:

    +---+ | a | +---+

    Event-Queue: b

    verlasse a()
    Callstack: leer
    Event-Queue: b

    betrete b():
    Callstack:

    +---+ | b | +---+

    Event-Queue: leer

    betrete alert('whoop')
    Callstack:

    +-------+ | alert | +---+---+ | b | +---+

    Event-Queue: leer

    verlasse alert('whoop')
    Callstack:

    +---+ | b | +---+

    Event-Queue: leer;

    verlasse b()
    Callstack: leer
    Event-Queue: leer

    Besonders interessant wird es dann, wenn man beginnt Funktions-Definitionen zu verschachteln. Zum Beispiel mit einem Closure.
    Wir schreiben eine Funktion, die eine Zahl "a" als Parameter entgegen nimmt und eine neue Funktion zurück gibt, die eine zweite Zahl "b" nimmt und das Ergebnis zurück gibt.

    function plus ( a ) {    return function ( b ) {       return a + b;    } } var plus5 = plus(5); plus5(3); // 8 var plus7 = plus(7); plus7(3); // 10 plus7(5); // 12

    Versuch dir vorzustellen, wie der Callstack nach jeder Anweisung aussieht.

    Wenn wir plus(5) betreten liegt nur ein Element auf dem Callstack, dann verlassen wir plus(5) wieder, der Stapel ist anschließend also wieder leer.

    Dann betreten wir den nächsten Funktionsaufruf plus5(3), es liegt wieder nur ein einziges Element auf dem Callstack, nämlich wieder nur der aktuelle Funktionsaufruf. Wie ich am Anfang geschrieben habe, werden auf dem Callstack die lokalen Variablen und Parameter gespeichert, aber der Parameter "a" gehört ja gar nicht zum aktuellen Funktion, sondern zur Funtion plus. Und diese ist zu diesem Zeitpunkt ja längst abgearbeitet. Der Browser hat sich in einem geheimen Gedächtnis trotzdem irgendwo gemerkt, dass der Parameter "a" zuletzt 5 war.

    Man nennt diese Programmier-Konstrukt einen Closure. Ein Closure ermöglicht es also Variablen und Parameter über den eigenen Ausführungskontext hinaus, für spätere Funktionsaufrufe zu merken.

    Das ist in JavaScript ein beliebtes Konzept und wird sehr viel eingesetzt, es stellt aber auch sehr besondere Ansprüche an die interne Speicherverwaltung. Woher soll der Browser wissen, wann er eine Variable entgültig wegwerfen darf?

    Müsste ich das Buch dann nicht wieder zurück ins Regal stellen, sprich die Funktion per Befehl für beendet erklären, damit sie nicht im Speicher bleibt und dessen Kapazität beansprucht?

    Wie ich gerade erklärt habe, kann eine Funktion auch noch Speicher belegen, wenn sie längst abgelaufen ist. Das ist ein wichtiges Feature von JavaScript. Die Entscheidung, wie lange welche Variablen vorgehalten werden, trifft in JavaScript nicht der Programmierer, sondern die eingebaute Speicher-Verwaltung.

    Oder passiert das automatisch?

    Jap, undzwar hauptsächlich durch die Garbage-Collection.

    Oder hängt es vom Inhalt der Funktion ab?

    Auch das.

    Wenn ich also über das DOM die einzelnen Elemente meiner Seite steuere, indem ich Funktionen nach dem Schema meines Code-Beispiels verwende, muss ich die dann irgendwie wieder "schließen", oder werden solche Funktionen nach vollbrachter Tat automatisch wieder "zurück ins Regal gestellt", und verbrauchen keinen Speicher mehr?

    Der JavaScript-Interpreter macht weiter im Text, aber er reserviert vielleicht trotzdem noch Speicher für vergangene Funktionsaufrufe, vielleicht auch unnötigen Speicher, wer weiß.

    Überhaupt, wie verhält sich das mit "inaktiven" Funktionen - welchen Einfluss übt deren schieres Vorhandensein im script auf die Performance einer Seite aus?

    Ja in kaum messbaren Bereichen. Darum solltest du dir keine Sorgen machen müssen. Entwickle dein Programm als erstes nur unter software-architektkonischen Gesichtspunkten und ignoriere Speicher-Performanz erst mal. Es ist unwahrscheinlich, dass das später zu deinem Flaschenhals wird.

    Wenn dein Programm später unter Speicherproblemen leidet, dann analysiere die Ursache und gehe gezielt dagegen vor, anstatt von vornerein an allen Ecken und Enden kleine Tweaks vorzunehmen.

    Da meine js-Seitensteuerung ziemlich umfangreich zu werden droht, mache ich mir so meine Gedanken über eine möglichst speicherschonende Programmierung...

    Mach dir die Gedanken, wenn du wirklich Probleme damit kriegst, nicht vorher.

    --
    “All right, then, I'll go to hell.” – Huck Finn

    Folgende Nachrichten verweisen auf diesen Beitrag:

    1. Hallo 1UnitedPower,

      vielen, vielen Dank für die ausführliche und lehrbuchreife Beschreibung der Materie!

      Das mit dem Stapelspeicher war mir noch aus dem Informatikunterricht in vorchristlicher Zeit in dunkler Erinnerung...

      Schlussendlich heißt das also, dass ich mit JavaScript gar keinen direkten Zugriff auf die Speicherverwaltung habe (und dass das offenbar auch keine Probleme bereitet).

      Naja, vielleicht mache ich mir da wirklich zuviele Gedanken.

      Es ist halt so, dass bei dem Webseiten-Projekt, an dem ich gerade tüftle, es im Grunde gar keine "festen" Strukturen gibt, das heißt, der komplette Inhalt der Seite, also alle Seitenelemente, einschließlich der Navigation, besteht aus animierten <div>-Boxen und ist zu 100% script-gesteuert.

      Wenn durch click auf eine Box (oder einen Button in einer Box) die "Seite" gewechselt wird (und davon gibt es viele!), werden über display:none / display:block neue Elemente (Boxen) geladen; wird eine Box angeclickt, verändert sich deren Form und Inhalt (teilweise auch der der anderen angezeigten Boxen); usw...

      Obwohl die Steuerung keine "komplizierten" Funktionen erfordert, sondern nur etwas Übersicht, ist klar, dass die Sache sehr umfangreich ist.

      Zwar gibt es dabei ein paar fest definierte Schemata (Layouts) für einzelne "Seiten" innerhalb der Webseite, die immer wieder Verwendung finden, - aber auch angesichts maximaler Vereinfachung der Abläufe und optimaler Nutzung von Synergie-Effekten ist und bleibt der Code: nunja, - umfangreich...

      Da hatte ich mir dann doch so meine Gedanken gemacht, ob es nicht irgendwann mal zu Rucklern bzw. Verzögerungen bei den Animationen / dem Seitenaufbau kommen könnte, und wollte da schonmal etwas vorbauen, - nicht, dass ich am Ende vor 1 Million Zeichen script-Code sitze und feststelle, dass ich mein Haus auf sandigem Fundament gebaut habe!

      Also,

      nochmal vielen Dank für die Informationen!

      Beste Grüße,

      Roadster.

  3. Hi,

    function closeinfoscreen() {var infoscreen = document.getElementById("infoscreen"); infoscreen.style.display = "none";};

    Hinter geschweiften Klammern kommt kein Semi ;-)

    Müsste ich das Buch dann nicht wieder zurück ins Regal stellen, sprich die Funktion per Befehl für beendet erklären, damit sie nicht im Speicher bleibt und dessen Kapazität beansprucht?

    In der Regel kommt am ende einer Funktion ein return (mit Rückgabewert).
    Einen Funktionsaufruf ist eigentlich wie eine Variable zu handhaben.

    was meinst du mit Speicher?

    1. der client läd sich vom Server die HTML (JS,CSS) Seite in den Browser
    2. TCP Verbindung wird getrennt.
    3. Browser arbeitet das Programm im Speicher ab und schmeißt nix weg.

    Wenn du natürlich unendlich viele Zahlen mit JS multiplizierst, kannst du den Arbeitsspeicher vom Client gut voll packen ;-0

    Viele Grüße aus LA

    -- ralphi
  4. @@Roadster:

    nuqneH

    function closeinfoscreen() {var infoscreen = document.getElementById("infoscreen"); infoscreen.style.display = "none";};

    var infoscreen = document.getElementById("infoscreen") sollte außerhalb der Funktion stehen. Wenn die Funktion mehrfach aufgerufen wird, würde jedesmal das Element "infoscreen" im DOM gesucht werden, obwohl es immer dasselbe ist.

    Was passiert, wenn die Funktion ausgeführt ist, das heißt, wenn ich das Buch gelesen habe?

    Müsste ich das Buch dann nicht wieder zurück ins Regal stellen, sprich die Funktion per Befehl für beendet erklären, damit sie nicht im Speicher bleibt und dessen Kapazität beansprucht?

    Ja, der Funktionscode bleibt erhalten und die Funktion kann immer wieder aufgerufen werden.

    Anders sieht es aus, wenn du die Funktion als Variable definierst:

    var closeinfoscreen = function () {     /* … */ }

    (Im Gegensatz zu function closeinfoscreen() { /* … */ } muss das aber vor dem ersten Aufruf der Funktion stehen.)

    Wenn du später closeinfoscreen = null setzt, ist die Funktion nicht mehr verfügbar. Ich nehme an, dass der Garbage-Collector auch den Code aus dem Speicher schmeißt. (Man korrigiere mich, falls dem nicht so sein sollte.)

    Qapla'

    -- „Talente finden Lösungen, Genies entdecken Probleme.“ (Hans Krailsheimer)
    1. Hallo Gunnar!

      nuqneH

      (Ist das klingonisch?)

      var infoscreen = document.getElementById("infoscreen") sollte außerhalb der Funktion stehen. Wenn die Funktion mehrfach aufgerufen wird, würde jedesmal das Element "infoscreen" im DOM gesucht werden, obwohl es immer dasselbe ist.

      Ja, die Geschichte mit den Variablen!

      Ehrlich, ich habe keine Ahnung, wo ich die am besten notiere, denn die Sache ist doch die:

      Wie ich in meiner Antwort auf 1UnitedPower schon schrieb, ist es so, dass meine ganze Seite aus animierten Boxen besteht - also zum besseren Verständnis:

      +---------------------------------------------+
      |                                             |
      |    +----------------+ +----------------+    |
      |    |     box 1      | |      box 2     |    |
      |    +----------------+ +----------------+    |
      |    +------+  +------+ +------+  +------+    |
      |    | box  |  | box  | |  box |  |  box |    |
      |    |      |  |      | |      |  |      |    |
      |    |  3   |  |  4   | |   5  |  |   6  |    |
      |    +------+  +------+ +------+  +------+    |
      |    +------+  +------+ +------+  +------+    |
      |    |box 7 |  |box 8 | | box 9|  |box 10|    |
      |    +------+  +------+ +------+  +------+    |
      |                                             |
      +---------------------------------------------+

      So in etwa könnte ein Layout für eine Seite innerhalb meiner Webseite aussehen (davon gibt es mehrere verschiedene).

      Dabei ist jede Box in dem Sinne animiert, dass sie einen "Auftritt" und einen "Abgang" hat, und je nachdem welche Box angeclickt wird, schrumpfen die (oder manche) Boxen auf einen reinen Text-Button zusammen, wobei das Bild in der Mitte der Box verschwindet, oder sie vergrößern sich zu einer großen Info-Box mit Textinhalt (oder sie sorgen dafür, dass das angezeigte Boxen-Set verschwindet und ein neues Set aus neuen Boxen - oder, wenn im konkreten Fall möglich, auch den selben Boxen, nur mit anderem Inhalt - erscheint).

      Mir ist klar, dass ich dabei hart an der Grenze zur pathologischen Megalomanie agiere, aber die Seite wirkt dabei tatsächlich weder "verspielt" noch "überladen" ---> das läuft alles sehr dezent ab und ist in einem recht minimalistischen Design gehalten / also ich habe mir dabei schon was gedacht und es grundsätzlich ja auch erfolgreich umgesetzt...

      Problematisch ist dabei allerdings, dass es eine riesige Menge an Variablen gibt, die mit der Seitensteuerung verbunden sind, denn wann immer ein neues Layout (bzw. eine neue "Seite") aus neuen (oder mit neuem Inhalt versehenen) Boxen geladen wird oder auch nur eine einzige Box angeclickt wird, müssen ja über das DOM alle, bzw. fast alle Elemente der Seitenansicht angesprochen werden!

      Ergo sind 95% aller Variablen Teil von Funktionen, die - wenigstens potentiell - mehrfach aufgerufen werden (und geschätzte 60-80%, die in mehreren verschiedenen Funktionen zum Einsatz kommen).

      Dementsprechend hatte ich meine erste Version des scriptes mit einer episch langen Liste an Variablen begonnen, auf die ich dann mit den Funktionen zurückgegriffen habe.

      Dann habe ich allerdings einen Artikel gelesen, wo es um die Performance von scripten ging, und da stand drin, dass man tunlichst vermeiden sollte, Variablen außerhalb von Funktionen zu schreiben, da diese dann "den Speicher verstopfen würden", wenn sie nur zahlreich genug wären.

      Über die Auslegung des Wortes "zahlreich" muss im Zusammenhang mit meiner Webseite nicht spekuliert werden:

      Die Beschreibung der folgenden copy&paste-Aktion erspare ich dir...

      Also, ich habe keine Ahnung, wohin mit den Variablen!

      Gruß,

      Roadster.

    2. Hallo!

      sprich die Funktion per Befehl für beendet erklären, damit sie nicht im Speicher bleibt und dessen Kapazität beansprucht?

      Ja, der Funktionscode bleibt erhalten und die Funktion kann immer wieder aufgerufen werden.

      Anders sieht es aus, wenn du die Funktion als Variable definierst:

      Nur als Anmerkung, eine Funktionsdeklaration kann ebenfalls mit null überschrieben werden.

      (Im Gegensatz zu function closeinfoscreen() { /* … */ } muss das aber vor dem ersten Aufruf der Funktion stehen.)

      Ja. Die Ausführungsreihenfolge ist eine andere. Intern erzeugen beide Formen eine Variable.

      Wenn du später closeinfoscreen = null setzt, ist die Funktion nicht mehr verfügbar.

      Ja. Das gilt auch für Funktionsdeklarationen.

      grüße
      Ralf

  5. Es ist doch grundsätzlich so, dass eine Funktion, die man ins script schreibt, ersteinmal nur wie ein Buch in einem Bücherregal abgelegt ist (ok, vielleicht nicht der gelungenste Vergleich).

    Was passiert, wenn die Funktion ausgeführt ist, das heißt, wenn ich das Buch gelesen habe?

    Müsste ich das Buch dann nicht wieder zurück ins Regal stellen, sprich die Funktion per Befehl für beendet erklären, damit sie nicht im Speicher bleibt und dessen Kapazität beansprucht?

    Der Code der Funktion bleibt so lange im Bücherregal, wie die Funktion definiert ist. Das ist nur logisch, weil der Sinn und Zweck von Funktionen meist der ist, mehrfach aufgerufen zu werden.

    Die Variablen, die in der Funktion angelegt werden, werden möglicherweise bei Verlassen der Funktion automatisch entsorgt – möglicherweise, weil du innerhalb einer Funktion weitere Funktionen erzeugen kannst, die auf Variablen der ursprünglichen Funktion zugreifen und erst später aufgerufen werden. Deine Eventlistener wären solche Funktionen:

    var mama = function () {   var bla = "Blabla"   var tochter = function () {     alert(bla);   }   window.setTimeout(tocher, 2000); }

    Da die Tochterfunktion auf die Variable bla zugreift, die in der Elternfunktion mama() definiert ist, kann bla nach Durchlauf von mama() nicht einfach entsorgt werden.