Gernot Back: onload einige Init-Funktionen immer, andere manchmal

Hallo an alle,

wie macht ihr das:

In einem größeren Projekt sollen einige JavaScript-Initialisierungsfunktionen immer aufgerufen werden, andere nur auf bestimten Seiten.

Den Handlermechanismus zum Aufklappen der Hauptnavigation (auch für IE), den brauche ich z.B. immer und initialisiere diesen immer. Die Handler für die Imagemap auf der Seite mit einer Deutschlandkarte, bei der beim Überfahren mit der Maus das Bild immer durch eine Variante mit entsprechend gehighlighteten Bundesländern ausgetauscht werden soll oder das Script, das sicherstellt, dass die Sitemap auch im IE einigermaßen gut dargestellt wird, brauche ich aber nur auf einzelnen Seiten und will es auch nicht immer einbinden.

Bisher mache ich das so, dass ich abfrage, ob eine entsprechende Initialisierungsfunktion überhaupt verfügbar ist und rufe sie auch nur dann auf.

Also etwa so:

  
window.onload = function () {  
   generalInit();  
   if(window.highlightMapInit) highlightMapInit();  
   if(window.siteMapInit) siteMapInit();  
}  

Das heißt aber natürlich, dass ich in jedem Dokument des gesamten Projekts diese Funktionen versuchsweise (mit "if") aufrufe. Irgendwie behagt mir das nicht und kommt mir "quick and dirty" vor. Bei sehr vielen Einzelseiten im Projekt, die einer speziellen Initialisierungsfunktion bedürfen, wird so vielleicht auch mal eine vergessen.

Nun ist mir auch noch diese Idee gekommen: Ich bastele mir seitenspezifisch eine Onloadfunktion als String zusammen und lasse diese onload über eval() ausführen:

  
window.onload = function () {  
   eval(onloadString);  
}  
  
if(!window.onloadString)  
   window.onloadString = "";  
onloadString += "generalInit();";  
  
  
// an ganz anderer Stelle im Dokument, davor oder danach:  
if(!window.onloadString)  
   window.onloadString = "";  
onloadString += "highlightMapInit();";  
  
// an ganz anderer Stelle im Dokument, davor oder danach:  
if(!window.onloadString)  
   window.onloadString = "";  
onloadString += "siteMapInit();";  
  

Das hat ja auch den Vorteil, dass ich diesen String an jeder Stelle im Dokument erweitern kann; den für die Imagemap z.B. in einem SCRIPT-Block nach dem MAP-Element, in dem auch die Funktion "highlightMapInit()" definiert wird.

Nun wird ja aber eval() oft als "evil" gescholten. Welche Methode haltet ihr für die bessere, die performantere? Gibt es noch andere Möglichkeiten? Vielleicht bin ich auch nicht der erste, der die Variante mit "eval()" erfunden hat und es wurde an anderer Stelle schon diskutiert. Ich habe aber bei einer ersten oberflächlichen Suche nichts gefunden.

Gruß Gernot

  1. Lieber Gernot,

    was spricht denn dagegen, dass jedes Script von Dir seine eigene Einbindung in die window.onload-Funktion vornimmt?

    Ich habe mir angewöhnt, alle Scripte extern auszulagern, als eigene Objekte zu definieren und ihre jeweils eigene Initialisierung vornehmen zu lassen. Da sie als eigene Objekte existieren, kann ich bequem die jeweils vorhandenen window.onload-Funktionen in ihnen als eine Eigenschaft "oldWinOnLoad" speichern (und andere Events wie z.B. window.onResize "verbiege" ich ebenso).

    Beispiel (externe .js-Datei): ---------------------------------------------

    meinObjekt = {  
        eineFunktion : function (param) {  
            code...  
        },  
      
        // Initialisierungsfunktion, um Einstellungen für meinObjekt vorzunehmen  
        init : function () {  
            ... Code ...  
        },  
      
        // onload-Handler erweitern  
        onloadEinbinden : function () {  
            if (typeof(window.onload) == "function")  
                // alten Handler speichern  
                meinObjekt.oldWinOnLoad = window.onload;  
            // onload-Handler in eine Funktion einwickeln  
            window.onload = function () {  
                if (meinObjekt.oldWinOnLoad)  
                    // existiert nur, wenn auch eine Funktion drinnesteckt  
                    meinObjekt.oldWinOnLoad(); // alte onload-Funktion(en) ausführen  
                meinObjekt.init();  
            };  
        } // hier natürlich KEIN KOMMA (weil keine weitere Methode/Eigenschaft mehr folgt)  
    }; // meinObjekt zu Ende  
      
    // Jetzt in den onload-Event einbinden:  
    meinObjekt.onloadEinbinden();
    

    ---------------------------------------------------------------------------

    Dieses Vorgehen ermöglicht es mir, beliebig meine Scripte auf Seiten einzubinden und dabei beliebig zu kombinieren. Ich muss sogar auf einer Seite (in meinem Dateimanager) ein bereits so eingebundenes Script ("Explorer" für das dynamische Auf- und Zuklappen, siehe Download-Seite) "patchen", damit es auf die gesonderten Anforderungen (Unterordner per AJAX nachladen) reagieren kann.

    Das geht auch sehr einfach, indem man bereits definierte Funktionen oder Objekte einfach erweitert...

    Beispiel von oben erweitert:

    meinObjekt.eineFunktion = function (params) {  
        // neuer Code für diese Funktion  
        meinObjekt.neueEigenschaft = 'irgendetwas';  
        return false;  
    };
    

    Liebe Grüße aus Ellwangen,

    Felix Riesterer.

    --
    ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
    1. Lieber Felix,

      was spricht denn dagegen, dass jedes Script von Dir seine eigene Einbindung in die window.onload-Funktion vornimmt?

      Nichts, wahrscheinlich ist dein Ansatz auch performanter. Meiner ist aber vielleicht auch für Kollegen, die weniger von JavaScript verstehen, leichter durchschaubar und damit auch pflegbar.

      Gruß Gernot

      1. Lieber Gernot,

        Meiner ist aber vielleicht auch für Kollegen, die weniger von JavaScript verstehen, leichter durchschaubar und damit auch pflegbar.

        ähm... was kann es einfacheres geben, als lediglich eine Javascript-Datei einzubinden, und sich dann keine weiteren Gedanken mehr über sie zu machen? Diese "unobtrusive"-Geschichte ist ja in dieser Hinsicht extremst pflegeleicht! Einbinden - schon fertig!

        Liebe Grüße aus Ellwangen,

        Felix Riesterer.

        --
        ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
        1. Hallo Felix,

          ähm... was kann es einfacheres geben, als lediglich eine Javascript-Datei einzubinden, und sich dann keine weiteren Gedanken mehr über sie zu machen? Diese "unobtrusive"-Geschichte ist ja in dieser Hinsicht extremst pflegeleicht! Einbinden - schon fertig!

          Die wenigsten meiner Kollegen kennen diese Art der objektorientierten Programmierung mit Literalen in JavaScript, wie du sie verfolgst. Ich muss gestehen, ich nutze sie auch noch kaum. Wenn da mal schnell einer eine zusätzliche Seite im bestehenden Projekt bauen soll, die einer speziellen Initialisierung bedarf, ist er aufgeschmissen, wenn er das Modell nicht kennt.

          Gruß Gernot

          1. Lieber Gernot,

            Wenn da mal schnell einer eine zusätzliche Seite im bestehenden Projekt bauen soll, die einer speziellen Initialisierung bedarf, ist er aufgeschmissen, wenn er das Modell nicht kennt.

            das klingt interessant! Erklärst Du mir den Aspekt "spezielle Initialisierung" etwas genauer? Ich kann mir das gerade nicht so recht vorstellen...

            Im übrigen sind Deine im Eingangsposting vorgestellten Techniken auch nicht gerade leicht zu durchschauen. Insbesondere mit eval() sehe ich hier viel Potential für Missverständnisse und "tut net!"-Frust.

            Liebe Grüße aus Ellwangen,

            Felix Riesterer.

            --
            ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
            1. Hallo Felix,

              das klingt interessant! Erklärst Du mir den Aspekt "spezielle Initialisierung" etwas genauer? Ich kann mir das gerade nicht so recht vorstellen...

              Speziell soll heißen, dass es dieser Initialisierung eben nur auf speziellen Seiten bedarf.

              Im übrigen sind Deine im Eingangsposting vorgestellten Techniken auch nicht gerade leicht zu durchschauen. Insbesondere mit eval() sehe ich hier viel Potential für Missverständnisse und "tut net!"-Frust.

              Bei mir sind es immer nur drei Zeilen, die -ohne viel nachzudenken- zu übernehmen sind, bei dir ein kompletter Programmierstil, der natürlich überlegen ist und den man mittelfristig vielleicht auch forcieren sollte.

              Gruß Gernot

              1. Lieber Gernot,

                Speziell soll heißen, dass es dieser Initialisierung eben nur auf speziellen Seiten bedarf.

                Du meinst, um zu prüfen, ob die Script-Datei überhaupt eingebunden wurde, oder ob nicht? Wenn Du Deine Scripte alle in objektorientierte Schreibe umwandelst, dann bedarf es keiner extra Initialisierung mehr!

                Bei mir sind es immer nur drei Zeilen, die -ohne viel nachzudenken- zu übernehmen sind, bei dir ein kompletter Programmierstil, der natürlich überlegen ist und den man mittelfristig vielleicht auch forcieren sollte.

                Ich denke noch anders: Bei mir ist es nur eine Zeile, nämlich das Einbinden der externen Script-Datei. Alles andere passiert ja ohne weiteres Zutun des Seitenautors/-pflegers.

                Außerdem lässt sich jedes Objekt über die eigentlich im onload-Event aufgerufene Initialisierung auch leicht "per Hand" initialisieren:

                if (meinObjekt)  
                    meinObjekt.init();
                

                Wo liegt jetzt genau Dein Problem?

                Liebe Grüße aus Ellwangen,

                Felix Riesterer.

                --
                ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
    2. Hallo Felix,

      »»     // onload-Handler erweitern  
      
      >     onloadEinbinden : function () {  
      >         if (typeof(window.onload) == "function")  
      >             // alten Handler speichern  
      >             meinObjekt.oldWinOnLoad = window.onload;  
      >         // onload-Handler in eine Funktion einwickeln  
      >         window.onload = function () {  
      >             if (meinObjekt.oldWinOnLoad)  
      >                 // existiert nur, wenn auch eine Funktion drinnesteckt  
      >                 meinObjekt.oldWinOnLoad(); // alte onload-Funktion(en) ausführen  
      >             meinObjekt.init();  
      >         };
      
      

      Ich glaube nicht, dass das den gewünschten Effekt erzielt. Das Ziel der ganze Sache ist es ja gewisse Init.-Funktionen erst aufzurufen, wenn die Seite fertig geladen ist. Du rufst hier die (Init-)Funktion, die zuvor in window.onlaod gelegen ist, einfach auf. Damit umgehst du das eigentliche Problem, nämlich mit der Liste der Funktionen umzugehen. Der Sinn der Sache war doch aber, dass dies erst geschieht, wenn die Seite geladen ist.

      Grüße
      Jasmin

      1. Liebe Jasmin,

        window.onload = function () {
                    if (meinObjekt.oldWinOnLoad)
                        // existiert nur, wenn auch eine Funktion drinnesteckt
                        meinObjekt.oldWinOnLoad(); // alte onload-Funktion(en) ausführen
                    meinObjekt.init();
                };
        Ich glaube nicht, dass das den gewünschten Effekt erzielt. Das Ziel der ganze Sache ist es ja gewisse Init.-Funktionen erst aufzurufen, wenn die Seite fertig geladen ist. Du rufst hier die (Init-)Funktion, die zuvor in window.onlaod gelegen ist, einfach auf. Damit umgehst du das eigentliche Problem, nämlich mit der Liste der Funktionen umzugehen. Der Sinn der Sache war doch aber, dass dies erst geschieht, wenn die Seite geladen ist.

        ich glaube, dass Du da etwas missverstanden hast!

        window.onload wird erst gefeuert, wenn die Seite zu Ende geladen hat. Daher kann ich window.onload eine (anonyme) Funktion zuweisen, die eben bei fertig geladenem Dokument ausgeführt wird. Die if-Verzweigung stellt sicher, dass zuvor in das onload-Event eingebundener Code nach wie vor bei onload ausgeführt wird.

        ... im Übrigen setze ich diese Vorgehensweise schon länger erfolgreich ein (und nicht nur ich)!

        Liebe Grüße aus Ellwangen,

        Felix Riesterer.

        --
        ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
        1. Hallo Felix,

          window.onload = function () {
                      if (meinObjekt.oldWinOnLoad)
                          // existiert nur, wenn auch eine Funktion drinnesteckt
                          meinObjekt.oldWinOnLoad(); // alte onload-Funktion(en) ausführen
                      meinObjekt.init();
                  };
          Ich glaube nicht, dass das den gewünschten Effekt erzielt.
          ich glaube, dass Du da etwas missverstanden hast!

          Ja, beim genaueren Hinsehen :D. Du hangelst dich praktisch von Objekt zu Objekt; raffiniert gemacht.

          Grüße
          Jasmin

  2. Nun wird ja aber eval() oft als "evil" gescholten. Welche Methode haltet ihr für die bessere, die performantere? Gibt es noch andere Möglichkeiten?

    statt einem String und eval könntest du auch das ganze über eine Funktion und einem Array machen. z.b. so:

    ( function(){  
    var onLoad = [];  
    window.addOnLoad = function(func)  
    {  
        if(typeof func == 'function') onLoad.push( func);  
    }  
    window.onload = function()  
    {  
    for(var i = 0; i < onLoad.length; i++) onLoad[i]();  
    }  
    })();  
    
    

    und dann in den jeweiligen Dateien:

    addOnLoad( generalInit );  
    ...  
    addOnLoad( highlightMapInit);  
    ...  
    addOnLoad( siteMapInit);  
    
    

    Über die Geschwindigkeit würd ich mir bei sowas keine Gedanken machen, auch wenn eval i.d.R. 10 Mal oder mehr langsamer ist, bedeutet das ein verlangsamung von nur wenigen Bruchtteilen einer Sekunde, also vernachlässigbar.

    Struppi.

    --
    Javascript ist toll (Perl auch!)
    1. Lieber Struppi,

      man könnte sich ja einen eigenen Loader für nachzuladende Script-Dateien schreiben, der dann im Einzelfall prüft, welches Script nachgeladen werden, und wie es initialisiert werden muss.

      Da käme Deine Array-Methode auch wieder ins Spiel. Ob das eine Lösung für Gernot wäre?

      Liebe Grüße aus Ellwangen,

      Felix Riesterer.

      --
      ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
  3. hi,

    Den Handlermechanismus zum Aufklappen der Hauptnavigation (auch für IE), den brauche ich z.B. immer und initialisiere diesen immer. Die Handler für die Imagemap auf der Seite mit einer Deutschlandkarte, bei der beim Überfahren mit der Maus das Bild immer durch eine Variante mit entsprechend gehighlighteten Bundesländern ausgetauscht werden soll oder das Script, das sicherstellt, dass die Sitemap auch im IE einigermaßen gut dargestellt wird, brauche ich aber nur auf einzelnen Seiten und will es auch nicht immer einbinden.

    Immer Einbinden halte ich gar nicht für schlecht bzw. generell unangebracht.
    Wenn ich mir den Overhead eines HTTP-Requests betrachte - dann lieber eine etwas größere Javascript-Ressource, als mehrere kleinere.

    Wenn du eine Funktionalität nur auf einer Seite brauchst, könntest du über location.pathname überprüfen, ob du dich auf dieser befindest.

    Oder wenn bestimmte Initialisierungsfunktionen bestimmte Elemente im Dokument voraussetzen, zunächst überprüfen, ob diese vorhanden sind (z.B. getElementById).

    gruß,
    wahsaga

    --
    /voodoo.css:
    #GeorgeWBush { position:absolute; bottom:-6ft; }