Gunnar Bittersmann: Referenz im Objektliteral

Ich hätte gern sowas wie

let myObject = {
	array1: [1, 2, 3],
	array2: [2, 3, 4]
};

Nur dass sich array2 aus array1 berechnen soll:

let myObject = {
	array1: [1, 2, 3],
};

myObject.array2 = myObject.array1.map(x => x + 1);

Kriegt man das irgendwie in das Objektliteral mit rein?

let myObject = {
	array1: [1, 2, 3],
	array2: myObject.array1.map(x => x + 1)
};

ergibt: ReferenceError: can't access lexical declaration `myObject' before initialization

let myObject = {
	array1: [1, 2, 3],
	array2: array1.map(x => x + 1)
};

ergibt: ReferenceError: array1 is not defined

let myObject = {
	array1: [1, 2, 3],
	array2: this.array1.map(x => x + 1)
};

ergibt: TypeError: this.array1 is undefined

LLAP 🖖

--
“When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
  1. Tach!

    Ich hätte gern sowas wie

    let myObject = {
    	array1: [1, 2, 3],
    	array2: [2, 3, 4]
    };
    

    Nur dass sich array2 aus array1 berechnen soll:

    let myObject = {
    	array1: [1, 2, 3],
    };
    
    myObject.array2 = myObject.array1.map(x => x + 1);
    

    Dann mach es so. Das sind exakt genauso viele Zeilen wie die Versuche, das als Literal hinzubekommen.

    Kriegt man das irgendwie in das Objektliteral mit rein?

    let myObject = {
    	array1: [1, 2, 3],
    	array2: myObject.array1.map(x => x + 1)
    };
    

    ergibt: ReferenceError: can't access lexical declaration `myObject' before initialization

    Die Zuweisung findest erst nach dem Auswerten des Literalausdrucks statt, weil erst dann fessteht, was da zugewiesen werden soll.

    let myObject = {
    	array1: [1, 2, 3],
    	array2: array1.map(x => x + 1)
    };
    

    ergibt: ReferenceError: array1 is not defined

    Auf Mitglieder eines Objekts kann man nur mit this zugreifen, sonst referenziert man lokale oder globale Variablen.

    let myObject = {
    	array1: [1, 2, 3],
    	array2: this.array1.map(x => x + 1)
    };
    

    ergibt: TypeError: this.array1 is undefined

    console.log(this) anstelle von this.array1.map() zeigt, dass this ein Window-Objekt enthält. Sieht so aus, als ob das Objekt noch nicht soweit fertig ist, dass man auf dessen Bestandteile zugreifen kann.

    Eine Konstruktorfunktion könnte helfen, aber nicht bei anonymen Objekten, soweit ich weiß.

    Wenn dein A-Problem ist, möglichst wenig Code an der Stelle stehen zu haben, dann schreib da einen Funktionsaufruf hin und im Funktionskörper an anderer Stelle erzeugst du das Objekt schrittweise, bevor du es zurückgibst.

    dedlfix.

    1. @@dedlfix

      Wenn dein A-Problem ist, möglichst wenig Code an der Stelle stehen zu haben,

      Hintergrund war, dass im Objektliteral noch eine Methode ist, die sich auf array2 bezieht, und ich dachte, dass array2 dafür vorher im Objektliteral auftauchen müsste.

      Muss es gar nicht; funzt:

      let myObject = {
      	array1: [1, 2, 3],
      
      	foo: () => { console.log(myObject.array2); }
      };
      
      myObject.array2 = myObject.array1.map(x => x + 1);
      
      myObject.foo(); // Array [ 2, 3, 4 ]
      

      LLAP 🖖

      --
      “When UX doesn’t consider all users, shouldn’t it be known as ‘Some User Experience’ or... SUX? #a11y” —Billy Gregory
      1. Tach!

        Wenn dein A-Problem ist, möglichst wenig Code an der Stelle stehen zu haben,

        Hintergrund war, dass im Objektliteral noch eine Methode ist, die sich auf array2 bezieht, und ich dachte, dass array2 dafür vorher im Objektliteral auftauchen müsste.

        Das muss es nur bei kompilierenden Sprachen, die den Code komplett in Maschinencode (oder Zwischencode) übersetzen und deshalb wissen müssen, worauf sich was bezieht.

        Muss es gar nicht; funzt:

        let myObject = {
        	array1: [1, 2, 3],
        
        	foo: () => { console.log(myObject.array2); }
        };
        
        myObject.array2 = myObject.array1.map(x => x + 1);
        
        myObject.foo(); // Array [ 2, 3, 4 ]
        

        Javascript ist aber (unabhängig von irgendwelchen Just-In-Time-Compileren, die quasi ein halbherziges Kompilat erzeugen) eine interpretierte Sprache. Da muss erst zur Laufzeit das Element da sein, auf das man sich beziehen möchte.

        Was aber nicht geht, und wofür ich da grad keine Erklärung habe, warum im folgenden Code eine Closure entsteht:

        let myObject = {
        	array1: [1, 2, 3],
        
        	foo: () => { console.log(this.array2); }
        };
        
        myObject.array2 = myObject.array1.map(x => x + 1);
        
        myObject.foo(); // undefined
        

        Ausgetauscht ist in foo() das myobject durch this. Aber das verweist nicht auf das Objekt sondern auf window (wenn man das dort mit console.log(this); prüft).

        Ahh, habs rausgefunden, mithilfe des TypeScript-Playgrounds.

        let myObject = {
        	foo: () => { console.log(this.array2); }
        };
        

        wird dort übersetzt zu

        var _this = this;
        var myObject = {
            foo: function () { console.log(_this.array2); }
        };
        

        Und da erinnerte ich mich, dass bei den Fat-Arrow-Funktionen () => {} das Typescript das this auf den äußeren Kontext legt. "... the ECMAScript 6 arrow syntax. Arrow functions capture the this where the function is created rather than where it is invoked"

        Eine herkömmliche anonymous-function-Notation löst das this erst zur Laufzeit auf. Wenn du dein Objekt so erstellst

        let myObject = {
        	array1: [1, 2, 3],
        
        	foo: function() { console.log(this.array2); }
        };
        

        dann gehts auch mit dem this. Merke: Fat-Arrow-Functions sind keine 1:1-Alternativschreibweise für die herkömmliche anonyme Funktionen.

        dedlfix.

        1. Hallo dedlfix,

          Ahh, habs rausgefunden, mithilfe des TypeScript-Playgrounds.

          let myObject = {
          	foo: () => { console.log(this.array2); }
          };
          

          wird dort übersetzt zu

          var _this = this;
          var myObject = {
              foo: function () { console.log(_this.array2); }
          };
          

          Und da erinnerte ich mich, dass bei den Fat-Arrow-Funktionen () => {} das Typescript das this auf den äußeren Kontext legt. "... the ECMAScript 6 arrow syntax. Arrow functions capture the this where the function is created rather than where it is invoked"

          Das wollte ich dir gerade auch schreiben 😉 bzw. das gilt nicht nur für TypeScript, sondern auch für ES6.

          Eine herkömmliche anonymous-function-Notation löst das this erst zur Laufzeit auf. Wenn du dein Objekt so erstellst

          let myObject = {
          	array1: [1, 2, 3],
          
          	foo: function() { console.log(this.array2); }
          };
          

          In ES2015 kannst du auch folgendes schreiben:

          let myObject = {
          	array1: [1, 2, 3],
          
          	foo() { console.log(this.array2); }
          };
          
          myObject.array2 = myObject.array1.map(x => x + 1);
          myObject.foo()
          

          Siehe auch: method definitions

          LG,
          CK

          1. Tach!

            Eine herkömmliche anonymous-function-Notation löst das this erst zur Laufzeit auf. Wenn du dein Objekt so erstellst

            let myObject = {
            	array1: [1, 2, 3],
            
            	foo: function() { console.log(this.array2); }
            };
            

            In ES2015 kannst du auch folgendes schreiben:

            let myObject = {
            	array1: [1, 2, 3],
            
            	foo() { console.log(this.array2); }
            };
            
            myObject.array2 = myObject.array1.map(x => x + 1);
            myObject.foo()
            

            Ja, das erste ist eine anonyme Funktion, die einer Eigenschaft zugewiesen ist, das andere ist eine benannte Funktion, sprich: Methode. Sieht so aus, als ob das am Ende dasselbe wäre, ist es aber dann noch nicht. Die Methode steht immer zur Verfügung, egal wo im Quelltext sie steht. Die anonyme Funktion in der Eigenschaft kann erst dann verwendet werden, wenn die Codeausführung die Zuweisung durchgeführt hat.

            dedlfix.

            1. Hallo dedlfix,

              Tach!

              Eine herkömmliche anonymous-function-Notation löst das this erst zur Laufzeit auf. Wenn du dein Objekt so erstellst

              let myObject = {
              	array1: [1, 2, 3],
              
              	foo: function() { console.log(this.array2); }
              };
              

              In ES2015 kannst du auch folgendes schreiben:

              let myObject = {
              	array1: [1, 2, 3],
              
              	foo() { console.log(this.array2); }
              };
              
              myObject.array2 = myObject.array1.map(x => x + 1);
              myObject.foo()
              

              Ja, das erste ist eine anonyme Funktion, die einer Eigenschaft zugewiesen ist, das andere ist eine benannte Funktion, sprich: Methode.

              Nein, meine Variante ist nur eine Kurzform deiner Variante. Es besteht prinzipiell kein Unterschied. Lies doch mal den Link durch, den ich dir verlinkt habe 😉

              Sieht so aus, als ob das am Ende dasselbe wäre, ist es aber dann noch nicht. Die Methode steht immer zur Verfügung, egal wo im Quelltext sie steht. Die anonyme Funktion in der Eigenschaft kann erst dann verwendet werden, wenn die Codeausführung die Zuweisung durchgeführt hat.

              Ich glaube, du verrennst dich da in was. Die Kurzform steht auch erst nach der „Zuweisung“ zur Verfügung, nur dass die Zuweisung bei der Objekt-Initialisierung passiert. Das ist nur eine Fortführung des Property-Value-Patterns:

              let a = 10;
              let foo = {
                a, b() { console.log(this.a); }
              };
              
              foo.b();
              

              Das ist äquivalent zu

              let a = 10;
              let foo = {
                a: a,
                b: function() { console.log(this.a); }
              };
              
              foo.b();
              

              LG,
              CK

              1. Tach!

                Ich glaube, du verrennst dich da in was.

                Nein, aber es kann sein, das ich das mit einer anderen Situation verwechsle.

                foo();
                function foo() { console.log('bar'); }
                

                Das obige geht, aber nicht:

                foo();
                var foo = function() { console.log('bar'); }
                

                dedlfix.

                1. Hallo dedlfix,

                  Ich glaube, du verrennst dich da in was.

                  Nein, aber es kann sein, das ich das mit einer anderen Situation verwechsle.

                  ja, sieht danach aus 😀 Vergleichbare Situation wäre:

                  foo.bar();
                  let foo = {
                    bar() { console.log("howdy world"); }
                  };
                  
                  baz.bar();
                  let baz = {
                    bar: function() { console.log("howdy world"); }
                  };
                  

                  Geht aber halt beides nicht g Oder, alternativ:

                  let foo = {
                    baz() { this.bar(); },
                    bar() { console.log("howdy world"); }
                  };
                  foo.baz();
                  
                  let baz = {
                    baz: function() { this.bar(); },
                    bar: function() { console.log("howdy world"); }
                  };
                  baz.baz();
                  

                  Das geht aber halt beides 😉

                  LG,
                  CK

  2. Es geht auch ohne this:

    const array1 = [1,2,3];
    let myObject = {
    	array1,
    	array2: array1.map(x => x + 1)
    };
    

    Die Semantik ist ein bißchen anders als in deiner Variante: array2 wird hier einmalig bei der Objekterzeugung berechnet und nicht bei jedem Aufruf von foo. Das führt auch dazu, dass array2 hier nicht automatisch (lies: aus Versehen) seinen Wert ändert, wenn sich array2 ändern sollte.

    1. Hallo 1unitedpower,

      Das führt auch dazu, dass array2 hier nicht automatisch (lies: aus Versehen) seinen Wert ändert, wenn sich array2 ändern sollte.

      Das ist ja ein ganz besonders spannendes Verhalten 😉

      Bis demnächst
      Matthias

      --
      Rosen sind rot.
      1. Ups, das sollte natürlich heißen, „… wenn sich array1 ändern sollte.“