2012-04-02 8 views
6

Ich versuche, dieses Puzzle Geist Javascript OOP Problem zu lösen.Javascript-Klassen und Variablenreferenzen

So habe ich die folgende Klasse:

var ClassA = function() { 
    this.initialize(); 
} 

ClassA.prototype = { 

    methods : ['alpha','beta','gama'], 

    initialize : function() { 
     for (var i in this.methods) { 
      this[this.methods[i]] = function() { 
       console.log(this.methods[i]); 
      } 
     } 
    } 
} 

var a = new ClassA(); 

Wenn ich jede Methode, die ich den Namen, es zu drucken erwarten nennen, nicht wahr? Aber hier ist, was ich bekommen:

a.alpha(); // returns gama ?!? 
a.beta(); // returns gama ?!? 
a.gama(); // returns gama 

Aber wenn meine Klasse wie folgt aussieht:

var ClassB = function() { 
    this.initialize(); 
} 

ClassB.prototype = { 

    methods : ['alpha', 'beta', 'gama'], 

    initialize: function() { 
     for (var i in this.methods) { 
      this.addMethod(this.methods[i]); 
     } 
    }, 

    addMethod: function(method) { 
     this[method] = function() { 
      console.log(method); 
     } 
    } 

} 

var b = new ClassB(); 

b.alpha(); // returns alpha 
b.beta(); // returns beta 
b.gama(); // returns gama 

Warum ist das passiert?

+0

Ist das nicht [die falsche Art und Weise] (http://stackoverflow.com/questions/3010840/loop-through-array-in-javascript#answer-3010848) von einem Array in JS durchlaufen? – PeeHaa

+0

@RepWhoringPeeHaa - Ja, eine einfache Schleife sollte verwendet werden, aber das ist hier nicht das Problem. – nnnnnn

+0

@nnnnnn Ich weiß, deshalb ist es ein Kommentar und keine Antwort :-) – PeeHaa

Antwort

6
for (var i in this.methods) { 
     this[this.methods[i]] = function() { 
      console.log(this.methods[i]); 
     } 
} 

Ihr Problem liegt hier. Wenn diese Schleife endet, ist i das letzte Element. Jede Funktion verwendet das gleiche i, also sind sie alle das letzte Element.

Wenn Sie addMethod verwenden, machen Sie eine Schließung, um den korrekten Wert zu "erfassen".

BEARBEITEN: Wenn Sie addMethod aufrufen, "kopieren" Sie den Wert, anstatt den Wert i zu verwenden, der sich mit jeder Schleifeniteration ändert.

+0

Ich beantworte eine Frage so fast jeden Tag ... http://stackoverflow.com/questions/9980209/register-onclick-events-from -dynamisch-div-array-rails-jquery/9980579 # 9980579 –

+0

Ich bin verwirrt ... Ist nicht console.log nur eine andere Funktion, und von addMethod bin ich nur wickeln es in einem anderen? – drinchev

+0

@drinchev: 'console.log' ist hier nicht wichtig. Wichtig ist hier, dass Sie beim Aufruf von "addMethod" den Wert in jede Funktion kopieren, aber wenn Sie ohne diese Funktion arbeiten, verwenden Sie denselben Wert. (Tut mir leid, wenn ich das nicht gut erkläre.) –

3

In Ihrem ersten Version:

initialize : function() { 
    for (var i in this.methods) { 
     this[this.methods[i]] = function() { 
      console.log(this.methods[i]); 
     } 
    } 
} 

Die Methoden, die Sie innerhalb initialize alle schaffen beziehen sich auf die gleiche i Variable aus initialize - und nach initialize läuft i hat den Wert "gama", so unabhängig davon, welche der Methoden Sie rufen das ist der Wert von i, dass sie an der Konsole anmelden werden. JS speichert den aktuellen Wert von i nicht zum Zeitpunkt der Erstellung der Methode.

JS für jede Funktion eine „Schließung“ erzeugt - Variablen in Ihrer initialize Funktion deklariert (das heißt i) weiterhin in Rahmen für die verschachtelte Funktion (en) auch nach initialize abgeschlossen sein.

Die zweite Version ruft addMethod jede Methode hinzufügen:

addMethod: function(method) { 
    this[method] = function() { 
     console.log(method); 
    } 
} 

... und so, wenn sie laufen sie auf ihre eigene „Kopie“ des method Parameter beziehen werden, weil dann ein separater Verschluss für jede der Methoden.

Edit: Siehe auch diese Frage: How do JavaScript closures work? (mehrere Antworten erklären dies deutlicher als ich).

1

Sie können Ihr erstes Beispiel beheben, indem Sie einen anonymen Verschluss ergänzt:

initialize : function() { 
    for (var i in this.methods) { 
     (function (i) { // anonymous closure 
      this[this.methods[i]] = function() { 
       console.log(this.methods[i]); 
      } 
     }).call(this, i); // use .call() if you need "this" inside 
    } 
} 

Jetzt wird es die gleiche Art und Weise wie Ihr zweites Beispiel arbeiten. "Anonym" bedeutet, dass die Schließung durch eine Funktion erfolgt, die keinen Namen hat und sofort aufgerufen wird, wenn sie "erstellt" wird.

Hinweis zur Seite: Verwenden Sie .call(this, ...)this innerhalb der aufgerufenen Funktion zu erhalten, oder Sie können var that = this tun, verwenden that statt this und in der Regel die Funktion aufrufen:

for (var i in this.methods) { 
    var that = this; 
    (function (i) { // anonymous closure 
     that[that.methods[i]] = function() { 
      console.log(that.methods[i]); 
     } 
    })(i); // Called normally so use "that" instead of "this"! 
} 
+1

Er hat das Problem bereits behoben, er wollte wissen, warum es überhaupt ein Problem war. –

+0

Ja, aber diese Art der Reparatur ist einfacher und einfacher. – TMS

+0

Ich stimme zu, aber es beantwortet immer noch nicht die ursprüngliche Frage. –

0

Nun, vor allem für nicht mehr verwenden (Eigenschaft in Objekt) führt Schleifen auf Arrays aus. Es ist alles Spaß und Spiel, bis jemand zum Array-Objekt vorstößt, was beides eine vollkommen vernünftige und sehr nützliche/beliebte Sache ist. Dies führt dazu, dass benutzerdefinierte Methoden zu Ihren for x in Array-Schleifen hinzugefügt werden.

Wie für das Problem, es tut genau das, was Sie ihm in Version 1 gesagt haben. Das Problem ist, dass, wenn Sie um es zu feuern, ich das letzte ist, was ich war, "Gamma". Wenn Sie eine Referenz als Argument an eine Funktion übergeben, behält die Funktion den Wert des Wertes bei, der übergeben wurde.