jobo: this und that - closures, Funktionen und Objekte (Crockford)

Hallo,

ein Beispiel von Crockford lautet:

  
function Container(param) {  
  
    function dec() {  
        if (secret > 0) {  
            secret -= 1;  
            return true;  
        } else {  
            return false;  
        }  
    }  
  
    this.member = param;  
    var secret = 3;  
    var that = this;  
  
    this.service = function () {  
        if (dec()) {  
            return that.member;  
        } else {  
            return null;  
        }  
    };  
}  
  
// mein Test:  
var myContainer = new Container('abc');  
for (var i=1; i<5;i++) {  
	alert(i +": "+myContainer.service());  
}	  
//liefert beim vierten Mal "null"  

Kann ich das aber auch mit JSON und einem Objekt so bauen, so dass die Containerfunktion zum Objekt wird?

Bei einem ersten Versuch komme ich mit this und that nicht zu Rande:

  
  
a = {  
 	b:"abc",  
	that:this,  
	testThis: function() {  
		alert(this);  
	},  
 	testThat:function() {  
		alert(this.that);  
	}  
 }  
 a.testThis(); // [object Object]  
 a.testThat(); // [object Window]  
  

Ich dachte, es müsste genau umgekehrt sein, weil "a.that" das this konserviert, das this, was sich auf Container bezieht. Und sonst (Aufruf 1) eben das aktuelle "this" das Windowobjekt ist. Ich dachte schon, ich hätte es kapiert. Nu, jetzt stehts aber kurz davor (;-).

Gruß

jobo

  1. Kann ich das aber auch mit JSON und einem Objekt so bauen, so dass die Containerfunktion zum Objekt wird?

    Nein. Die Funktion erzeugt einen eigenen scope, den du mit einem Objekt nicht hast.

    Ich dachte, es müsste genau umgekehrt sein, weil "a.that" das this konserviert, das this, was sich auf Container bezieht. Und sonst (Aufruf 1) eben das aktuelle "this" das Windowobjekt ist. Ich dachte schon, ich hätte es kapiert. Nu, jetzt stehts aber kurz davor (;-).

    Nein, die Eigenschaft that ist der Kontext in dem das Objekt erzeugt wird, also window. this ist das Objekt.

    Struppi.

  2. Kann ich das aber auch mit JSON und einem Objekt so bauen, so dass die Containerfunktion zum Objekt wird?

    Wieso willst du das tun?

    Diesen Trick mit »that« verwendet man, damit man die Methoden des Objektes als First-Class-Objekt übergeben kann, ohne das Objekt mit zu übergeben. Das passiert etwa beim Event-Handling, bei setTimeout oder sonstiger funktionaler Programmierung. Über die Closure hat die Methode dann immer noch Zugriff auf das ursprüngliche Objekt.

    Im Beispiel, was du gepostet hast, ist dieser Trick noch nicht nötig, weil die Methode nicht anders als über objekt.methode() ausgeführt wird. In solchen Fällen zeigt »this« immer brav auf »objekt« und du kannst this ohne Probleme verwenden.

    Die anderen Fälle habe ich hier beschrieben:

    Methoden eigener Objekte in anderen Kontexten ausführen
    Alternativlösungen zur Kontext-Problematik: bind() und bindAsEventListener()
    bzw. in dem neueren Text
    Organisation von JavaScripten: Objektverfügbarkeit und this-Kontext

    Du kannst bei Objects ebenfalls eine Closure oder Binding verwenden, wenn du die Referenz auf die Instanz brauchst und funktional arbeiten willst.

    Mit Closure - eine Funktion als Expression erzeugen, sofort ausführen, die Methode darin ist eine Closure:

    var obj = (function () {  
       var obj = {  
          prop : 'foo',  
          method : function () { alert(obj.prop); }  
       };  
       return obj;  
    })();  
      
    setTimeout(obj.method, 1);
    

    Mit Binding:

    var obj = {  
       prop : 'foo',  
       method : function () { alert(obj.prop); }  
    };  
      
    setTimeout(obj.method.bind(obj), 1);
    

    a = {

    b:"abc",
    that:this,
    testThis: function() {
    alert(this);
    },
    testThat:function() {
    alert(this.that);
    }
    }
    a.testThis(); // [object Object]
    a.testThat(); // [object Window]

    
    >   
    > Ich dachte, es müsste genau umgekehrt sein, weil "a.that" das this konserviert  
      
    Es konserviert ein this, und zwar das Objekt, zu dem this auflöste in dem Kontext, indem es aufgelöst wurde. Das ist bei obigem Code das globale Objekt window. Dieses zwischenzuspeichern ist i.d.R. unnötig.  
      
    
    > das this, was sich auf Container bezieht.  
      
    Du kannst nicht innerhalb eines Object-Literals einen Verweis auf das gerade erzeugte Object anlegen. Das kannst du natürlich im Nachhinein: `a.that = a`{:.language-javascript}. Aber welchen Zweck sollte das haben.  
      
    Wenn du die Bedeutung von this noch nicht verstanden hast, helfen auch folgende Texte:  
    <http://molily.de/js/organisation-instanzen.html>  
    <http://dmitrysoshnikov.com/ecmascript/chapter-3-this/>  
      
    Mathias
    
    1. Mit Binding:

      var obj = {

      prop : 'foo',
         method : function () { alert(obj.prop); }
      };

      setTimeout(obj.method.bind(obj), 1);

        
      Das muss `alert(this.prop);`{:.language-javascript} heißen - durch das Binding ist this zuverlässig nutzbar.
      
    2. Hallo,

      bzw. in dem neueren Text
      Organisation von JavaScripten: Objektverfügbarkeit und this-Kontext

      Nur nebenbei, bei Deinem 1. Beispiel zum "this-problem" kommen bei mir zwei alerts "start wurde aufgerufen ...", erst das Dritte ist "verzögert ...". Vielleicht spinnt mein FF grade.

        
      <button id="button">button</button>  
      <script>  
      [code lang=javascript]  
      var Modul = {  
          eigenschaft : "Eigenschaftswert",  
          start : function () {  
              // Funktioniert:  
              alert("start wurde aufgerufen\n" +  
                  "this.eigenschaft: " + this.eigenschaft);  
              setTimeout(this.verzögert, 100);  
              document.getElementById("button").onclick = this.handler;  
          },  
          verzögert : function () {  
              // Fehler: this verweist auf window  
              alert("verzögert wurde aufgerufen\n" +  
                  "this.eigenschaft: " + this.eigenschaft);  
          },  
          handler : function (e) {  
              // Fehler: this verweist auf das Element, dem der Event-Handler anhängt  
              alert("handler wurde aufgerufen\n" +  
                  "this.eigenschaft: " + this.eigenschaft);  
          },  
      };  
      Modul.start();  
      
      

      </script>

      [/code]

      Gruß

      jobo

      1. Hallo,

        Nur nebenbei, bei Deinem 1. Beispiel zum "this-problem" kommen bei mir zwei alerts "start wurde aufgerufen ...", erst das Dritte ist "verzögert ...". Vielleicht spinnt mein FF grade.

        Hier übrigenswill mein FF zwei Zeile über "var instanz ..." kein Semikolon am Ende:

          
        <button id="button2">button2</button>  
        <script>  
        [code lang=javascript]  
        function Konstruktor () {}  
        Konstruktor.prototype = {  
            eigenschaft : "Eigenschaftswert",  
            start : function () {  
                // Funktioniert:  
                alert("start wurde aufgerufen\n" +  
                    "this.eigenschaft: " + this.eigenschaft);  
                setTimeout(this.verzögert, 100);  
                document.getElementById("button2").onclick = this.handler;  
            },  
            verzögert : function () {  
                // Fehler: this verweist auf window  
                alert("verzögert wurde aufgerufen\n" +  
                    "this.eigenschaft: " + this.eigenschaft);  
            },  
            handler : function (e) {  
                // Fehler: this verweist auf das Element, dem der Event-Handler anhängt  
                alert("handler wurde aufgerufen\n" +  
                    "this.eigenschaft: " + this.eigenschaft);  
            }  
        };  
        var instanz = new Konstruktor();  
        instanz.start();  
        
        

        </script>
        [/code]

        Und auch zwei Alerts "start wurde aufgerufen ...". Komisch.

        Gruß

        jobo

        1. Hier übrigenswill mein FF zwei Zeile über "var instanz ..." kein Semikolon am Ende:

          Was hast du für eine Version?

          Und auch zwei Alerts "start wurde aufgerufen ...". Komisch.

          Das kann nicht sein, nicht mit dem Code.

          Struppi.

          1. Hallo,

            Hier übrigenswill mein FF zwei Zeile über "var instanz ..." kein Semikolon am Ende:

            Was hast du für eine Version?

            S.o. mit Semikolon am Ende der handler-Funktion bringt aber auch das neue den Alert korrekt anzeigende Fenster ein "missing } after property list".

            In der Json-Notation haben doch Semikolons am Ende einer Property-Deklaration keinen Platz, oder?

            Gruß

            jobo

            1. S.o. mit Semikolon am Ende der handler-Funktion bringt aber auch das neue den Alert korrekt anzeigende Fenster ein "missing } after property list".

              Ich finde es gerade schwer deinen Schilderungen zu folgen. Der code von Mathias ist absolut korrekt und sollte auch keinen Fehler werfen.

              In der Json-Notation haben doch Semikolons am Ende einer Property-Deklaration keinen Platz, oder?

              Wenn sie beendet ist schon.

              Struppi.

              1. Hallo,

                S.o. mit Semikolon am Ende der handler-Funktion bringt aber auch das neue den Alert korrekt anzeigende Fenster ein "missing } after property list".

                Ich finde es gerade schwer deinen Schilderungen zu folgen.

                Echt? Das Semikolon nach der hanlder-Funktion ist wirklich eine genaue Beschreibung. Ich bekomme den Fehler vom FF, ich denk mir das ja nicht aus (;-), Zeile 22 (s. meine Pfeilmarkierung unten, deshalb mal kein Code-Highlighting):

                <button id="button">button</button>
                <script>
                function Konstruktor () {}
                Konstruktor.prototype = {
                    eigenschaft : "Eigenschaftswert",
                    start : function () {
                        // Funktioniert:
                        alert("start wurde aufgerufen\n" +
                            "this.eigenschaft: " + this.eigenschaft);
                        setTimeout(this.verzögert, 100);
                        document.getElementById("button").onclick = this.handler;
                    },
                    verzögert : function () {
                        // Fehler: this verweist auf window
                        alert("verzögert wurde aufgerufen\n" +
                            "this.eigenschaft: " + this.eigenschaft);
                    },
                    handler : function (e) {
                        // Fehler: this verweist auf das Element, dem der Event-Handler anhängt
                        alert("handler wurde aufgerufen\n" +
                            "this.eigenschaft: " + this.eigenschaft);
                    };
                -----^
                };
                var instanz = new Konstruktor();
                instanz.start();
                </script>
                 Der code von Mathias ist absolut korrekt und sollte auch keinen Fehler werfen.

                In seinem Beispiel davor steht da ein Komma. Ich denke da sollte vermutlich garnix stehen, oder? Json sieht doch so aus:

                  
                obj = {  
                  prop1:"bla",  
                  prop2:function() {},  
                  prop3:"letzer Wert" // kein Komma und vor allem kein Semikolon dahinter?  
                };  
                
                

                In der Json-Notation haben doch Semikolons am Ende einer Property-Deklaration keinen Platz, oder?

                Wenn sie beendet ist schon.

                Genau.

                Gruß

                jobo

                1. Ich finde es gerade schwer deinen Schilderungen zu folgen.

                  Echt?

                  Ja, ich bin aber momentan angeschlagen. Ich hatte die Worte "zwei Zeile über", gestern nicht richtig verstanden oder gelesen.

                  Das Semikolon nach der hanlder-Funktion ist wirklich eine genaue Beschreibung.

                  Jetzt schon ;-)

                  handler : function (e) {
                          // Fehler: this verweist auf das Element, dem der Event-Handler anhängt
                          alert("handler wurde aufgerufen\n" +
                              "this.eigenschaft: " + this.eigenschaft);
                      };
                  -----^

                  Du hast vollkommen recht, da gehört kein Semikolon hin.

                  In seinem Beispiel davor steht da ein Komma. Ich denke da sollte vermutlich garnix stehen, oder? Json sieht doch so aus:

                  JSON ist nichts besonderes - es ist die Schreibweise, die JS verwendet um Objekte oder Arrays zu definieren. Man nennt es dort Objekt-literal oder die literale Schreibweise.

                  Aber du hast Recht, nach der letzten Attribut definition sollte kein Komma stehen, da der IE das mit einem Fehler quittiert.

                  Struppi.

                  1. Hallo,

                    Aber du hast Recht, nach der letzten Attribut definition sollte kein Komma stehen, da der IE das mit einem Fehler quittiert.

                    Dann betrifft das auch das Beispiel darüber, "Modul":

                    handler : function (e) {
                            // Fehler: this verweist auf das Element, dem der Event-Handler anhängt
                            alert("handler wurde aufgerufen\n" +
                                "this.eigenschaft: " + this.eigenschaft);
                        },

                    -----^

                    Da steht am Ende ein Komma. Den FF stört das nicht.

                    Gruß

                    jobo

                    1. Aber du hast Recht, nach der letzten Attribut definition sollte kein Komma stehen, da der IE das mit einem Fehler quittiert.

                      Dann betrifft das auch das Beispiel darüber

                      [...]

                      Da steht am Ende ein Komma. Den FF stört das nicht.

                      Ja, das ist ein IE Problem http://javascript.jstruebig.de/javascript/728#ie_komma

                      Struppi.

      2. Nur nebenbei, bei Deinem 1. Beispiel zum "this-problem" kommen bei mir zwei alerts "start wurde aufgerufen ...", erst das Dritte ist "verzögert ...". Vielleicht spinnt mein FF grade.

        Nein, die Verzögerung beträgt 100ms ist also kurz. Der dritte aufruf kommt nach dem drücken auf den Button.

        Struppi.

        1. Hallo,

          Nur nebenbei, bei Deinem 1. Beispiel zum "this-problem" kommen bei mir zwei alerts "start wurde aufgerufen ...", erst das Dritte ist "verzögert ...". Vielleicht spinnt mein FF grade.

          Nein, die Verzögerung beträgt 100ms ist also kurz. Der dritte aufruf kommt nach dem drücken auf den Button.

          Ja, so der Code. Aber nicht mein FF im Moment. Auch nicht bei der nächsten Variante. Man sieht es ja am Text im alert-Fenster. "start..." "verzögert..." "handler..."

          Gruß

          jobo

          1. Ja, so der Code. Aber nicht mein FF im Moment. Auch nicht bei der nächsten Variante. Man sieht es ja am Text im alert-Fenster. "start..." "verzögert..." "handler..."

            Dafür gibt es keine sinnvolle Erklärung. Ist bei mir (FF 3.6.13) auch nicht so.

            Struppi.

            1. Hallo,

              Ja, so der Code. Aber nicht mein FF im Moment. Auch nicht bei der nächsten Variante. Man sieht es ja am Text im alert-Fenster. "start..." "verzögert..." "handler..."

              Dafür gibt es keine sinnvolle Erklärung. Ist bei mir (FF 3.6.13) auch nicht so.

              Nee, im Chrome auch nicht und im neuen Fenster von FF (3.6.13) auch nicht. Aber ich schwöre, im noch offenen davorigen Fenster war/ist es so (;-).

              Gruß

              jobo

      3. Hallo,

        kurze Rückmeldung, die beiden Quellcode-Fehler (überflüssiges Komma bzw. Semikolon) habe ich korrigiert, danke fürs Melden.

        Mathias