Citrusleo: Problem mit "with" (Mehrere Anweisungen für ein Objekt)

Hallo zusammen,
Ich bin neu im Forum und habe ein, nun nicht direkt ein Problem, sondern eher eine Verständisfrage zu with.
Ich habe einen HTML5 Drag&Drop Test gemacht(nur für den Firefox) und darin kam folgender Code vor:

  
document.getElementsByClassName("dropBox")[0].addEventListener("drop",drop,false);  
document.getElementsByClassName("dropBox")[1].addEventListener("drop",drop,false);  

Nun wollte ich noch ein Event hinzufügen und dachte mir, warum nicht gleich Beide auf einen Streich:

  
with(document.getElementsByClassName("dropBox"))  
{  
addEventListener("dragover",dragover,false);  
addEventListener("drop",drop,false);  
}  

Aber jetzt kommt die Überraschung:
Die Klasse "dropBox" enthält zwei Elemente, aber ich habe weder [0], noch [1] angesprochen, also strenggenommen ein Fehler. Aber was macht Firefox? Anstatt besagten Error auszuspucken, durchläuft er die HTMLCollection und fügt allen Elementen die EventListener hinzu.(Neueste Version, 3.6.6)
Ich bin etwas ratlos! Vielen Dank schonmal im Voraus für die Hilfe.

  1. Hallo,

    with(document.getElementsByClassName("dropBox")) {

    Mit getElementsByClassName wird ein array erzeugt/ausgegeben. Angesprochen wird wohl immer noch das Array und nicht die einzelnen Elemente. Das Problem wäre dann weniger die Umsetzung von with, sondern vielleicht wie der Browser solch ein event beim ganzen Array verwirklicht bzw. abfragt.

    Grüsse

    Cyx23

    --
    HTML5
    1. Mit getElementsByClassName wird ein array erzeugt/ausgegeben.

      Nicht ganz richtig: getElementsByClassName gibt ein [objectHTMLCollection] zurück, welches einem Array jedoch ähnelt

      »»Angesprochen wird wohl immer noch das Array und nicht die einzelnen Elemente.

      Doch genau das macht Firefox! Als ob es eine Foreach Schleife, wie in PHP wäre! Er durchläuft das "Array" und gibt jedem die EventListener.

      »»Das Problem wäre dann weniger die Umsetzung von with, sondern vielleicht wie der Browser solch ein event beim ganzen Array verwirklicht bzw. abfragt.

      Ich habe nicht vor dieses Verhalten auszunutzen. Es hat mich nur verwirrt und ich war mir nicht sicher, ob es ein Bug ist.

      1. Hallo,

        »»Angesprochen wird wohl immer noch das Array und nicht die einzelnen Elemente.

        Doch genau das macht Firefox! Als ob es eine Foreach Schleife, wie in PHP wäre! Er durchläuft das "Array" und gibt jedem die EventListener.

        nein, da ging ich erstmal davon aus, dass hier bestenfalls die Elemente in der Summe versorgt werden, nur ein EventListener für alle Elemente. Aber letztlich wie schon ähnlich im thread gepostet einfach als ein event für/über window (oder ggf. document).

        Grüsse

        Cyx23

  2. Hallo,

    Die Klasse "dropBox" enthält zwei Elemente, aber ich habe weder [0], noch [1] angesprochen, also strenggenommen ein Fehler.

    Das sehe ich auch so.

    Ich bin etwas ratlos! Vielen Dank schonmal im Voraus für die Hilfe.

    Welche Hilfe erwartest du denn konkret?

    Wenn es wirkich stimmt, ist es doch ziemlich kurios. Teste doch mal andere Browser oder google nach "with HTMLCollection". Vielleicht ist es sogar irgendwo so spezifiziert... man weiß ja nie.

    Auf dieses Verhalen verlassen würde ich mich aber nicht, falls es nicht wirklich irgendwie zum Standard gehört.

    Die with-Anweisung ist ohnehin mit Vorsicht zu genießen, d.h. besser zu vermeiden, weil oft nur schwer erkennbar ist, welche Variablen eigentlich dabei angesprochen werden.

    Sowas:

      
    var i=0, coll= document.getElementsByClassName("dropBox");  
    while (coll[i++]) { coll[i]("drop",drop,false); }  
    
    

    wäre z.B. eine Alternative (ungetestet).

    Gruß, Don P

    1. Natürlich eher so:

        
      var i=0, coll= document.getElementsByClassName("dropBox");  
      while (coll[i++]) { coll[i].addEventListener("drop",drop,false); }  
      
      

      Gruß, Don P

    2. Welche Hilfe erwartest du denn konkret?

      Eine Stellungsnahme.

      Wenn es wirkich stimmt, ist es doch ziemlich kurios. Teste doch mal andere Browser oder google nach "with HTMLCollection". Vielleicht ist es sogar irgendwo so spezifiziert... man weiß ja nie.

      Hab ich schon, kein Ergebnis.

      Die with-Anweisung ist ohnehin mit Vorsicht zu genießen, d.h. besser zu vermeiden, weil oft nur schwer erkennbar ist, welche Variablen eigentlich dabei angesprochen werden.

      Danke, dass wollte ich immer schon mal wissen! Ich war mir sowieso nicht so sicher, ob ich sie verwenden soll.

      1. Hallo,

        Die with-Anweisung ist ohnehin mit Vorsicht zu genießen, d.h. besser zu vermeiden, weil oft nur schwer erkennbar ist, welche Variablen eigentlich dabei angesprochen werden.

        Danke, dass wollte ich immer schon mal wissen! Ich war mir sowieso nicht so sicher, ob ich sie verwenden soll.

        Besser nicht.
        with sucht die Variablen zuerst im angebenen Scope, in deinem Fall also im Scope der HTML-Collection. Wenn dort eine Eigenschaft mit dem angegebenen Namen existiert, wird sie auch verwendet. Wenn nicht, wird im nächst höheren Scope gesucht usw. usf., wenn nötig bis hinauf zum globalen Objekt (window).
        Daher weiß man oft nicht so genau, welche Variable/Eigenschaft von welchem Objekt nun eigentlich im with-Block angesprochen wird.

        Von der Logik her müsste es so sein: Weil keine Methode "addEventListener" im HTMLCollection-Objekt existiert, wird das document-Objekt herangezugen, wo sie nämlich existiert, und deshalb auch verwendet wird. Also sollten deine Eventhandlerfunktionen ans document-Objekt gehängt werden.

        Bist du wirklich sicher, dass sie nur deinen "dropbox"-Elementen zugewiesen sind und nicht etwa dem ganzen document?

        Gruß, Don P

        1. Von der Logik her müsste es so sein: Weil keine Methode "addEventListener" im HTMLCollection-Objekt existiert, wird das document-Objekt herangezugen, wo sie nämlich existiert, und deshalb auch verwendet wird. Also sollten deine Eventhandlerfunktionen ans document-Objekt gehängt werden.

          Bei with(document.getElementsByClassName("dropBox")) ist document ist nicht in der Scope Chain, sondern nur der Rückgabewert dieses Ausdrucks.
          Allerdings ist window in der Scope Chain, das hat auch eine addEventListener-Methode. Ergo, das ganze Dokument bekommt den Handler (wie du auch sagst).

          Mathias

          1. Hallo,

            Bei with(document.getElementsByClassName("dropBox")) ist document ist nicht in der Scope Chain, sondern nur der Rückgabewert dieses Ausdrucks.

            Ach ja, so ganz hatte ich das nicht durchschaut. Jedenfalls ist es kein Fehler des Browsers, sondern liegt an der Tücke der with-Anweisung.
            Ich verwende sie nur ganz selten, und nur dann, wenn ich absolut sicher bin, dass alle im with-Block verwendeten Variablen auch wirklich im angegebenen Scope liegen.

            sowas ist ja kein Problem:

            var mannschaft = { name: "", rang: "" };  
              
            with( mannschaft ) {  
              
             name = "dt. NationalElf";  
             rang = "vielleicht bald im Halbfinale";  
              
            }
            

            Aber sonst können leicht seltsame Dinge geschehen, wie man in diesem Thread sieht...

            Gruß, Don P

  3. Hallo,

    immer wenn du einen Bezeichner notierst, z.B. »addEventListener«, dann greift die Identifier Resolution. Sprich, der JavaScript-Interpreter löst den Namen zu einem Objekt auf. Diese Namensauflösung erfolgt nach festgelegten Regeln. Und zwar schaut der Interpreter nacheinander an einer Kette von Objekten nach. Diese Kette ist die sogenannte Scope Chain. Die enthält standardmäßig das globale Objekt window sowie, innerhalb von Funktionen, interne Variablenobjekte, an denen lokale Funktionsvariablen gespeichert werden.

    Wenn du irgendwo »addEventListener« notierst, dann geht der Interpreter also einfach die Scope Chain durch, um die entsprechende Methode zu finden. Wenn keine lokale Variable dieses Namens existiert und keine in einer höherliegenden Funktion, dann wird der Interpreter bei window.addEventListener fündig.

    with macht nun, wie Don P sagt, nichts anderes, als diese Scope Chain um ein Objekt zu erweitern, nämlich an erster Stelle. In deinem Beispiel ist das die HTMLCollection, die getElementsByClassName zurückgibt.

    <div class="dropBox">foo</div>  
    <script>  
    [code lang=javascript]function test () {  
      with(document.getElementsByClassName("dropBox"))  {  
        addEventListener("dragover", dragover, false);  
        addEventListener("drop", drop, false);  
      }  
    }  
    test();
    

    </script>[/code]

    Der Bezeichner addEventListener wird hier an drei Objekten gesucht:
    1. an der HTMLCollection
    2. am Variablenobjekt des Aufrufs der Funktion test
    3. am globalen window-Objekt

    Bei 1. und 2. wird der Interpreter nicht fündig.
    alert(document.getElementsByClassName("dropBox").addEventListener);
    gibt »undefined« aus. Eine lokale Variable namens addEventListener existiert auch nicht.

    Was also passiert: Du registrierst beim window-Objekt einen Event-Handler, der reagiert natürlich auf sämtliche dragover/drop-Ereignisse im gesamten Dokument. Das ist vermutlich nicht das, was du intendiert hast. Allerdings kann es durchaus einfacher sein, dokumentweite Event-Handler zu registrieren, anstatt die passenden Elemente mit Methoden wie getElementsByClassName herauszusuchen und vielen einzelnen Elementen Handler zu verpassen.

    with ist für solche Zweck jedenfalls nicht gedacht. Wenn du eine HTMLCollection durchlaufen willst, dann kannst du das mit einer konventionellen for- oder while-Schleife tun:

    var elements = document.getElementsByClassName("dropBox");  
    for (var i = 0, element; element = elements[i]; i++) {  
       element.addEventListener("dragover", dragover, false);  
       element.addEventListener("drop", drop, false);  
    }
    

    Zum Nachlesen:
    http://dmitrysoshnikov.com/ecmascript/chapter-2-variable-object/
    http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/
    </archiv/2010/2/t194977/#m1304595>

    Mathias

  4. Danke ersteinmal für die vielen Kommentare!
    @molily: Sehr kompliziert, aber ich glaube, ich habe alles verstanden.

    with ist für solche Zweck jedenfalls nicht gedacht. Wenn du eine HTMLCollection durchlaufen willst, dann kannst du das mit einer konventionellen for- oder while-Schleife tun:

    var elements = document.getElementsByClassName("dropBox");  
    for (var i = 0, element; element = elements[i]; i++) {  
       element.addEventListener("dragover", dragover, false);  
       element.addEventListener("drop", drop, false);  
    }
    

    Das hatte ich nicht vor, ich wollte eigentlich nur vermeiden, zweimal documents.getElementsByClassName() zu schreiben..
    Natürlich ist es sinvoller es in einer variable abzuspeichern:

    var dropBox_elements = document.getElementsByClassName("dropBox");  
    dropBox_elements[0].addEventListener//(..)
    

    Wie gesagt, ich habe es durch Zufall bemerkt.

    Bist du wirklich sicher, dass sie nur deinen "dropbox"-Elementen zugewiesen sind und nicht etwa dem ganzen document?
    3. am globalen window-Objekt

    Ihr habt Recht, es wurde dem ganzen document zugewiesen (window objekt meine ich.)

    So, jetzt weiß ich, wann ich with benutzen kann, und wann eher nicht.
    Danke, das Thema ist für mich abgeschlossen. ;-)