2012-04-02 2 views
6

Я пытаюсь решить эту проблему с головоломкой Javascript OOP.Классы Javascript и ссылки на переменные

Поэтому у меня есть следующий класс:

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(); 

Когда я звоню каждый метод, который я ожидал, чтобы напечатать имя его, не так ли? Но вот что я получаю:

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

Но когда мой класс выглядит следующим образом:

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 

Почему это происходит?

+0

Не так ли [неправильный путь] (http://stackoverflow.com/questions/3010840/loop-through-array-in-javascript#answer-3010848) цикла через массив в JS? – PeeHaa

+0

@RepWhoringPeeHaa - Да, должен использоваться простой цикл, но это не проблема. – nnnnnn

+0

@nnnnnn Я знаю, что это комментарий, а не ответ :-) – PeeHaa

ответ

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

Ваша проблема здесь. Когда этот цикл завершается, последним элементом является i. Каждая функция использует то же самое i, поэтому они все являются последним элементом.

Когда вы используете addMethod, вы делаете закрытие, чтобы «захватить» правильное значение.

EDIT: Когда вы вызываете addMethod, вы «копируете» значение, а не используете значение i, которое изменяется с каждой итерацией цикла.

+0

Я отвечаю на такой вопрос почти каждый день ... http://stackoverflow.com/questions/9980209/register-onclick-events-from -dynamically-created-div-array-rails-jquery/9980579 # 9980579 –

+0

Я в замешательстве ... Разве console.log не просто другая функция, а методом addMethod я просто обертываю его в другой? – drinchev

+0

@drinchev: 'console.log' здесь не имеет значения. Важно то, что когда вы вызываете 'addMethod', вы копируете значение в каждую функцию, но когда вы делаете это без него, вы используете то же значение. (Извините, если я не объясню это хорошо.) –

3

В вашей первой версии:

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

Методы, которые вы создаете в initialize все относятся к одной и той же i переменной от initialize - и после того, как initialize пробегов i имеет значение "gama", поэтому независимо от того, какой из методов вы вызываете это значение i, чтобы они заходили на консоль. JS не сохраняет текущее значение i во время создания метода.

JS создает «замыкание» для каждой функции - переменные, объявленные в вашей initialize функции (то есть, i) продолжают находиться в области видимости для вложенной функции (ы) даже после того, как initialize закончил.

Второй вариант требует addMethod для добавления каждого метода:

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

... и поэтому, когда они работают, они будут относиться к своей «копии» параметр method, потому что тогда есть отдельное замыкание для каждого из методов.

Редактировать: Смотрите также этот вопрос: How do JavaScript closures work? (несколько ответов там объясняют это более четко, чем я).

1

Вы можете исправить свой первый пример, добавив анонимное окончание:

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 
    } 
} 

Теперь он будет работать точно так же, как ваш второй пример. «Анонимный» означает, что закрытие производится функцией, которая не имеет имени и называется мгновенно, поскольку она «создана».

Примечание вбок: использовать .call(this, ...) сохранить this внутри вызываемой функции, или вы можете сделать var that = this, используйте that вместо this и вызовите функцию обычно:

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

Он уже исправил проблему, он хотел знать, почему это было проблемой в первую очередь. –

+0

Да, но этот способ исправления более прост, проще. – TMS

+0

Согласен, но он все еще не отвечает на исходный вопрос. –

0

Ну, в первую очередь отказаться от использования для (свойство в объекте) на массивах. Это все забава и игры, пока кто-то не прототипы объекта Array, который является совершенно разумной и очень полезной/популярной задачей. Это приведет к тому, что пользовательские методы будут добавлены в ваш x для циклов массива.

Что касается проблемы, это делает именно то, что вы сказали ей сделать в версии 1. Проблема в том, что к тому времени, когда вы обходитесь, чтобы стрелять в нее, я последнее, что я был, «гамма». Когда вы передаете ссылку в функцию в качестве аргумента, функция переходит в состояние значения по мере ее передачи.

Смежные вопросы