klawischnigg: Aufrufende Variable im Objekt ermitteln

Hi there,

steh' wieder einmal auf dem Schlauch; gibt es bei folgendem Code eine Möglichkeit, den Namen der "aufrufenden" Variablen im Objekt selbst zu erfahren?

var irgendEineVariable = new irgendEinObjekt();

Also, die Frage ist, kann man dem Objekt irgendwie mitteilen, daß es von irgendEineVariable instanziert wurde? Ich würd' das für einen Eventhandler im Objekt benötigen...

  1. Hallo klawischnigg,

    in diesem Fall ist irgendEinObjekt eine Konstruktorfunktion, und das erzeugte Objekt sollte ein Property constructor besitzen, das eine Referenz auf diese Funktion enthält. Die Funktion hat ein name Property.

    Beispiel mit String:

    const xyz = new String("Hallo");
    console.log(xyz.constructor.name);  // String
    

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hi there,

      in diesem Fall ist irgendEinObjekt eine Konstruktorfunktion, und das erzeugte Objekt sollte ein Property constructor besitzen, das eine Referenz auf diese Funktion enthält. Die Funktion hat ein name Property.

      Beispiel mit String:

      const xyz = new String("Hallo");
      console.log(xyz.constructor.name);  // String
      

      Ja, aber genau das xyz kenn' ich innerhalb des Objekts leider nicht...

      1. Hallo klawischnigg,

        hä? xyz ist doch dein irgendeinObjekt. Oder mir hat wiedermal jemand die Glaskugel gesandstrahlt.

        Das Verfahren ist allerdings nicht ganz narrensicher.

        const irgendeineVariable = String;
        const irgendeinObjekt = new irgendeineVariable();
        console.log(irgendeinObjekt .constructor.name);  // immer noch String
        

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hi there,

          hä? xyz ist doch dein irgendeinObjekt. Oder mir hat wiedermal jemand die Glaskugel gesandstrahlt.

          Ja, ich muß jetzt leider weg, ich meld mich später, danke erst einmal...

    2. Hallo Rolf B,

      Ich glaube, er möchte xyz als Ergebnis haben.

      Bis demnächst
      Matthias

      --
      Du kannst das Projekt SELFHTML unterstützen,
      indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
      1. Hallo Matthias,

        ja, scheint so. Mich hat das "instanziiert" durcheinandergebracht. Instanziiert wurde von irgendeineFunktion. Das richtige Wort wäre vermutlich "referenziert".

        Aber dann gilt Dedlfixens Aussage: Objekte können an vielen Strippen baumeln, keine davon ist irgendwie besonders.

        Wenn es die Notwendigkeit gibt, in einem Eventhandler auf eine bestimmte Variable zuzugreifen, die dieses Objekt enthält, wäre das weniger trivial. Es gibt eigentlich nur genau einen Grund, warum man das tun wollte: um sie zu verändern. D.h. um das Objekt gegen ein anderes auszutauschen. Das würde ich als Antipattern bezeichnen, denn man weiß ja nicht wer das Objekt noch alles hält, und ein Überschreiben von irgendeineVariable würde nicht allen Haltern einer Referenz auf dieses Objekt bekannt. Das mag für den konkreten Fall okay sein, es ist im Allgemeinen aber ein Kandidat für Objektspagetti.

        Variante 1: Man hinterlegt nach dem new eine Extramethode, die den Replace durchführen kann:

        let irgendeineVariable = new IrgendeinObjekt();
        
        irgendeineVariable.replaceYourselfWith = newObj => irgendeineVariable = newObj;
        
        // ---------
        // Im Eventhandler, e.foo enthält das Objekt
        
        function EventHandler(e) {
           e.foo.replaceYourselfWith({ x,y,z });
        }
        

        Ich sehe aber instinktiv rot, wenn ich sowas sehe.

        Variante 2: Namensgesteuert

        window.irgendein = [];   // Global (oder global in einem Modul)
        
        irgendein.objekt = new irgendeinObjekt();
        irgendein.objekt.key = "objekt";
        
        // ---------
        // Im Eventhandler, e.foo enthält das Objekt
        
        function EventHandler(e) {
           irgendein[e.foo.key] = { x,y,z };
        }
        

        Find ich konzeptionell genauso schrecklich. Ich mag nicht in der Zwangslage stecken, in der man da nicht dran vorbei kommt.

        Relevanter Trick ist jedenfalls, am Objekt einen irgendwie gearteten Verweis auf den Speicherort zu hinterlegen, der im Eventhandler benötigt wird.

        Rolf

        --
        sumpsi - posui - obstruxi
  2. Tach!

    steh' wieder einmal auf dem Schlauch; gibt es bei folgendem Code eine Möglichkeit, den Namen der "aufrufenden" Variablen im Objekt selbst zu erfahren?

    var irgendEineVariable = new irgendEinObjekt();
    

    Eine Variable ist nur eine von mehreren Möglichkeiten, eine Referenz auf ein Objekt zu halten. Wer alles eine Referenz darauf hält, ist nicht ermittelbar. Jedenfalls nicht direkt, meines Wissens.

    Also, die Frage ist, kann man dem Objekt irgendwie mitteilen, daß es von irgendEineVariable instanziert wurde? Ich würd' das für einen Eventhandler im Objekt benötigen...

    this zeigt auf das Objekt, und das kannst du auch weitergeben. Die Variable wird dazu nicht benötigt. Mit den Namen der Variablen zu hantieren ist auch nicht die feine englische Art.

    dedlfix.

  3. Lieber klawischnigg,

    var irgendEineVariable = new irgendEinObjekt();
    

    Du willst also in dem Objekt, welches von irgendEineVariable referenziert wird, den Variablennamen wissen? Das klingt wirklich sehr verschrägt, wäre meines Wissens aber nur so lösbar:

    var irgendEineVariable = new irgendEinObjekt("irgendEineVariable");
    

    Natürlich muss der Konstruktor diesen übermittelten Wert auch sinnvoll weiter verarbeiten. Nur was daran "sinnvoll" sein soll, verstehe ich nicht.

    Ich würd' das für einen Eventhandler im Objekt benötigen...

    Mich beschleicht der Verdacht, dass Du mit einer sehr indirekten Vorgehensweise etwas zu lösen versuchst, das sich bestimmt sehr direkt und einfacher lösen lässt. Dazu müsstest Du aber schon wesentlich mehr Code springen lassen und zeigen, was Du da genau tust.

    Liebe Grüße

    Felix Riesterer

    1. Hi there,

      Du willst also in dem Objekt, welches von irgendEineVariable referenziert wird, den Variablennamen wissen? Das klingt wirklich sehr verschrägt, wäre meines Wissens aber nur so lösbar:

      var irgendEineVariable = new irgendEinObjekt("irgendEineVariable");
      

      Du hast das Problem sehr schön erkannt, und an diese Lösung hab ich auch schon gedacht, wahrscheinlich geht's eh nicht eleganter.

      Natürlich muss der Konstruktor diesen übermittelten Wert auch sinnvoll weiter verarbeiten. Nur was daran "sinnvoll" sein soll, verstehe ich nicht.

      Ich würd' das für einen Eventhandler im Objekt benötigen...

      Mich beschleicht der Verdacht, dass Du mit einer sehr indirekten Vorgehensweise etwas zu lösen versuchst, das sich bestimmt sehr direkt und einfacher lösen lässt.

      Naja, "irgendEinObjekt" ist eine Konstruktorfunktion, die ein JS-Window erzeugt, und die eine Funktion "closeWindow()" beinhaltet, die dieses Window mit allem, was darin dargestellt wird, löscht. Zusätzlich generiert diese Funktion auch einen Button, der die "closeWindow()"-Funktion aufruft. Und genau da fangen meine Probleme an - mit button.addEventlistener('click',closeWindow) funktionierts klarerweise nicht, weil der Kontext falsch ist, mit button.addEventlistener('click',this.closeWindow) geht's auch nicht, weil sich "this" in dem Fall auf den Button und nicht auf das Objekt bezieht. Richtig wäre natürlich button.addEventlistener('click',irgendEineVariable.closeWindow), aber irgendEineVariable hab ich wieder in dem Kontext nicht zur Verfügung. Es wäre ganz einfach, den Eventhandler nach dem Erzeugen des Objekts anzuhängen, aber genau das möchte ich eben nicht, weil das Erzeugen des Schliessen-Buttons mit all seinen Funktionen resp. Funktionalitäten eine Methode von "irgendEinObjekt" bleiben soll...

      1. Hallo,

        mit button.addEventlistener('click',this.closeWindow) geht's auch nicht, weil sich "this" in dem Fall auf den Button und nicht auf das Objekt bezieht

        warum speicherst du das gewünschte „this“ nicht:

        dieses = this;
        Button.addEventlistener('click',dieses.closeWindow);
        

        Gruß
        Jürgen

        1. Hi there,

          mit button.addEventlistener('click',this.closeWindow) geht's auch nicht, weil sich "this" in dem Fall auf den Button und nicht auf das Objekt bezieht

          warum speicherst du das gewünschte „this“ nicht:

          dieses = this;
          Button.addEventlistener('click',dieses.closeWindow);
          

          sollte es wirklich so einfach sein 😉? Ich sag ja, manchmal steht man auf dem Schlauch, im Prinzip funktioniert's so, die Funktion wird aufgerufen. (Da habe ich jetzt noch ein paar andere Probleme, aber das hat damit voraussichtlich nicht wirklich was zu tun, danke für das auf die Sprünge helfen...)

        2. Tach!

          mit button.addEventlistener('click',this.closeWindow) geht's auch nicht, weil sich "this" in dem Fall auf den Button und nicht auf das Objekt bezieht

          Das this bezieht sich nur dann auf den Button wenn der Aufrufkontext, in dem der Eventhandler gesetzt wird, eine Methode des Buttons ist. Dann könnte man aber auch gleich this.addEventListener… schreiben. Wenn der Aufrufkontext eine Methode des Windows ist, dann zeigt this auf das Window.

          Das Problem ist aber, dass zum Zeitpunkt des Events der Kontext ein anderer ist, und ein im Eventhandler verwendetes this auf den Kontext zum Zeitpunkt des Ereignisses verweist und nicht mehr auf das Window. Die Methode wird zwar aufgerufen, aber so, als würde man sie statisch aufrufen, und nicht als Methode eines konkreten Window-Objekts.

          warum speicherst du das gewünschte „this“ nicht:

          dieses = this;
          Button.addEventlistener('click',dieses.closeWindow);
          

          Damit löst man das Problem nicht. Der Eventhandler bindet sozusagen nur die Adresse der Methode, aber es wird keine Closure erzeugt, die irgendwelche Daten aus dem derzeitigen Aufrufkontext festhalten würde, auf die man zum späteren Zeitpunkt des Events zugreifen könnte. Deshalb ist es egal, ob man das this direkt schreibt oder eine Indirektion über eine weitere Variable verwendet.

          Wenn sich allerdings closeWindow im selben Scope befindet, dann bildet dieser Scope die Closure und man kann in closeWindow auf dieses zugreifen. Aber besser ist, den Aufruf der Arrow-Funktion aus dem nachfolgenden Beispiel zu verwenden, dann kann closeWindow auch sonstwo definiert sein und man braucht keine Hilfsvariable.

          Nehmen wir folgendes Beispiel.

          <button id="foo" data-x=42>foo</button>
          <button id="bar" data-x=23>bar</button>
          
          <script>
          const foo = document.querySelector('#foo');
          const bar = document.querySelector('#bar');
          
          console.log(foo.dataset);
          console.log(bar.dataset);
          
          foo.clickHandler = function() {
              console.log('click', this.dataset);
              bar.addEventListener('click', this.clickHandler);
          }
          foo.addEventListener('click', foo.clickHandler);
          
          </script>
          

          Zwei Buttons, jeder mit einem eigenen Wert in data-x, der zur Kontrolle jeweils mit einem console.log() ausgegeben wird. Danach geben wir dem foo-Button eine neue Eigenschaft namens clickHandler mit einer Methode als Inhalt. Und ich füge einen Eventhandler für sein Click-Ereignis hinzu. Diese Zuweisung befindet sich im globalen Kontext, this verweist auf das globale window-Objekt, deshalb muss ich foo.clickHandler übergeben.

          Wenn wir auf den foo-Button klicken, ruft es den clickHandler auf. Der Event-Mechanismus sorgt dafür, dass this auf den foo-Button zeigt, und console.log() zeigt die 42 von foo.

          Als zweite Anweisung wird dem click-Event von bar ein Eventhandler zugewiesen. Das this zeigt noch immer auf foo, so wie es uns der Eventhandlermechanismus übergeben hat. Klicken wir nun auf den bar-Button, wird jetzt ebenfalls diese Methode aufgerufen. Aber die Ausgabe ist nun die 23 von bar. Wie auch schon zuvor wurde vom Eventmechanismus das this auf den Auslöser gesetzt, der nun bar ist. Uns interessiert nur die console.log()-Ausgabe, die Zeile mit dem bar.addEventListener ignorieren wir mal.

          Aber für den nächsten Versuch ändern wir sie zu

              bar.addEventListener('click', () => this.clickHandler());
          

          und klicken nacheinander auf foo und bar. Beide Male wird 42 ausgegeben. Hier kommt eine Besonderheit der Arrow-Funktion zum tragen, denn diese bindet in this den Kontext zum aktuellen Zeitpunkt, also einen Verweis auf foo. Im Eventhandler wird die Methode clickHandler() von foo im Kontext von foo aufgerufen, ohne dass wir zum Eventzeitpunkt wissen müssen, dass wir uns in der Instanz von foo befinden. Wir verwenden einfach this.

          Ändern wir zum Spaß noch die Zeile in einen Aufruf einer herkömmlichen anonymen Funktion

              bar.addEventListener('click', function() { this.clickHandler(); });
          

          bekommen wir eine Fehlermeldung beim Klick auf bar (und vorher foo)

          this.clickHandler is not a function
          

          denn nun wird als this der Button bar durchgereicht, der keine Methode namens clickHandler hat.

          Fazit: Arrow-Funktion verwenden. Oder was mit .bind() zaubern, wenn modernes Javascript nicht möglich ist.

          dedlfix.

          1. Hallo dedlfix,

            natürlich sind Pfeil-Funktionen die elegantere Lösung. Leider sperrt man damit die IEs aus, sie werfen einen Syntaxerror und das wars. Kein progressive Enhancement, einfach ein Platzverweis.

            Gruß
            Jürgen

            1. Tach!

              natürlich sind Pfeil-Funktionen die elegantere Lösung. Leider sperrt man damit die IEs aus, sie werfen einen Syntaxerror und das wars. Kein progressive Enhancement, einfach ein Platzverweis.

              Ja, wenn man den noch braucht, dann muss man weiterhin herkömmlich arbeiten. Aber die bind-Geschichte sollte der wenigstens können. Das finde ich syntaktischer auch mehr geradeaus, als sich eine Closure um den clickHandler herum zu bilden.

              Alternativ dürfte auch ein Transpiler wie TypeScript oder Babel funktionieren, mit denen man modernen Code schreiben kann, der für alte Browser in herkömmlichen Code übersetzt wird.

              dedlfix.

      2. Hallo klawischnigg,

        okay, das wird klarer.

        Jürgen war schneller, die this/that Technik zur Bildung einer Closure funktioniert natürlich auch.

        Eine andere Möglichkeit ist Function.prototype.bind. Der bildet die Closure intern:

        function PopupWindow() {
           // do magic
           let btnClose = // more magic to create the button
           btnClose.addEventListener("click", this.closeWindow.bind(this));
           // continue with magic
        }
        
        PopupWindow.prototype.closeWindow = function() {
           // close the window
        }
        

        this.closeWindow.bind(this) erzeugt eine neue Funktion, deren this auf das in dem Moment übergebene this festgenagelt ist.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hi there,

          okay, das wird klarer.

          Jürgen war schneller, die this/that Technik zur Bildung einer Closure funktioniert natürlich auch.

          Eine andere Möglichkeit ist Function.prototype.bind. Der bildet die Closure intern:

          function PopupWindow() {
             // do magic
             let btnClose = // more magic to create the button
             btnClose.addEventListener("click", this.closeWindow.bind(this));
             // continue with magic
          }
          
          PopupWindow.prototype.closeWindow = function() {
             // close the window
          }
          

          this.closeWindow.bind(this) erzeugt eine neue Funktion, deren this auf das in dem Moment übergebene this festgenagelt ist.

          Ja, sehr fein, das muß ich auch probieren, vielen Dank für's Damitbeschäftigen...

      3. Lieber klawischnigg,

        button.addEventlistener('click',closeWindow) funktionierts klarerweise nicht, weil der Kontext falsch ist,

        dann verwende eine anonyme Funktion:

        button.addEventListener(
          "click",
          function (event) {
            irgendEineVariable.closeWindow();
          }
        );
        

        Liebe Grüße

        Felix Riesterer

        1. Hi there,

          button.addEventlistener('click',closeWindow) funktionierts klarerweise nicht, weil der Kontext falsch ist,

          dann verwende eine anonyme Funktion:

          button.addEventListener(
            "click",
            function (event) {
              irgendEineVariable.closeWindow();
            }
          );
          

          Naja, das würde gar nichts ändern, weil im Objekt hab ich irgendeineVariable eben nicht zur Verfügung...

      4. @@klawischnigg

        Naja, "irgendEinObjekt" ist eine Konstruktorfunktion, die ein JS-Window erzeugt, und die eine Funktion "closeWindow()" beinhaltet, die dieses Window mit allem, was darin dargestellt wird, löscht. Zusätzlich generiert diese Funktion auch einen Button, der die "closeWindow()"-Funktion aufruft. Und genau da fangen meine Probleme an - mit button.addEventlistener('click',closeWindow) funktionierts klarerweise nicht, weil der Kontext falsch ist, mit button.addEventlistener('click',this.closeWindow) geht's auch nicht, weil sich "this" in dem Fall auf den Button und nicht auf das Objekt bezieht. Richtig wäre natürlich button.addEventlistener('click',irgendEineVariable.closeWindow)

        Warum? Du musst den Eventhandler doch nicht für den Button registrieren.

        Den kannst du doch für dein Window-Dingens registrieren und darin abfragen, ob das Event durch Click auf den Schließen-Button ausgelöst wurde (event delegation).

        😷 LLAP

        --
        „Sag mir, wie Du Deine Maske trägst, und ich sage Dir, ob Du ein Idiot bist.“ —@Ann_Waeltin
        1. Hi there,

          Warum? Du musst den Eventhandler doch nicht für den Button registrieren.

          Prinzipiell hast Du natürlich recht.

          Den kannst du doch für dein Window-Dingens registrieren und darin abfragen, ob das Event durch Click auf den Schließen-Button ausgelöst wurde (event delegation).

          Allerdings hielte ich eine solche Vorgangsweise weder für besonders elegant (ok, d'rauf gepfiffen) noch für "nachhaltig" (in dem Sinne, das ich so eine Konstruktion natürlich auch in anderen Programmen oder Skripten verwenden möchte.) Unter pragmatischen Gesichtspunkten (deren großer Freund ich im Grunde genommen ja durchaus bin) ist Dir natürlich recht zu geben...

          PS.: ich hab die Geschichte dann letzten Endes mit dem Vorschlag von JürgenB gelöst, das war am einfachsten und schnellsten zu implementieren (womit wir wieder bei "pragmatisch" wären...;))

          1. @@klawischnigg

            Allerdings hielte ich eine solche Vorgangsweise weder für besonders elegant (ok, d'rauf gepfiffen) noch für "nachhaltig" (in dem Sinne, das ich so eine Konstruktion natürlich auch in anderen Programmen oder Skripten verwenden möchte.)

            Ich halte das sowohl für elegant als auch für nachhaltig, wenn sich jede Komponente zentral um die in ihr ausgelösten und von ihr zu behandelnden Events kümmert.

            Dann hast du zusammengeörige Dinge auch zusammen im Code zu stehen: bspw. das Fenster schließen willst du ja nicht nur beim Click auf den Schließen-Button, sondern auch beim Drücken der Escape-Taste.

            😷 LLAP

            --
            „Sag mir, wie Du Deine Maske trägst, und ich sage Dir, ob Du ein Idiot bist.“ —@Ann_Waeltin