Назначение замыканий в циклах для .prototype

Я работаю над библиотекой Javascript, в которой я не могу загрязнять глобальное пространство имен, и все мои переменные должны содержаться в одной или двух глобальных переменных. В настоящее время я столкнулся с особым случаем, когда мне нужно использовать замыкания, но то, что я обычно пытаюсь сделать, не работает, и это беспокоит меня в течение некоторого времени. Поиск дал мне только традиционный метод закрытия, который я обычно использую.

[...]
addFilters: function(filters) {
  for(filter in filters) {
    this.filters[filter] = filters[filter];
    this.Image.prototype[filter] = function() { //closures, how do they work?
      return (function(image, filter, arguments) {
        image.addQueue(filter, arguments);
      })(this, filter, arguments);
    };
  }
},
[...]

В приведенном выше фрагменте функции Image.prototype (и image.addQueue) неправильно захватывают «фильтр», поэтому каждый раз устанавливается последний фильтр в итерации for..in.

Полный код с выделенными соответствующими разделами: http://pastebin.com/UVFTVPkh

Живая демонстрация: http://ian0.com/code/js/ube/demo.html< /а>


person Community    schedule 10.04.2011    source источник


Ответы (2)


У тебя фабричная функция немного шаткая. Нужно назвать его аргументы, а самовызов выполняется некорректно. Это намного более очевидно, если вы просто используете отдельную функцию для создания функции, назначенной this.Image.prototype[filter].

function generateProtoFunc(image, filter, arguments) {
    return function(filter, arguments) {
        image.addQueue(filter, arguments);
    };
}

// snip...

for (filter in filters) {
    this.filters[filter] = filters[filter];
    this.Image.prototype[filter] = generateProtoFunc(this, filter, arguments);
}

Вот правильный способ сделать это, используя немедленный вызов функции:

for (filter in filters) {
    this.filters[filter] = filters[filter];
    this.Image.prototype[filter] = (function(image, filter, arguments) {
        return function(filter, arguments) {
            image.addQueue(filter, arguments);
        };
    })(this, filter, arguments);
}
person Matt Ball    schedule 10.04.2011
comment
Я всегда так делал, и у меня никогда не было этой проблемы раньше. Не могли бы вы показать мне, что я делаю неправильно? - person ; 10.04.2011
comment
Разве это не приведет к тому же результату? Я просто избегаю использования другой функции-конструктора, заключая анонимную функцию в круглые скобки, чтобы я мог немедленно вызвать ее. - person ; 10.04.2011
comment
@Ian: нет, потому что ты не правильно написал. Я обновлю свой ответ, чтобы показать разницу. - person Matt Ball; 10.04.2011

Замыкание привязано к области, в которой оно объявлено, а не к конкретным значениям переменных в этой области на момент объявления.
Я объясняю, почему ваш код не работал. И зная почему, вы увидите, что есть более простой способ сделать это.
Все те функции, которые вы объявляете в каждом цикле FOR, связаны с одной и той же областью действия (областью функции addFilters), поэтому при выполнении замыканий они читать переменные из одной и той же области, и поэтому они получают одинаковые значения.
Следовательно, ключевым здесь является привязка каждого замыкания к другой области, и это то, что вы делаете, заключая замыкания в анонимную функцию: т. е. вы создаете уникальный объем для каждого замыкания.

Но... нужен ли вам анонимный вызов функции для создания области?
Ответ - НЕТ.
Вы можете создать область действия более читабельным способом, используя оператор with.

Вот ваш код, использующий этот подход:

[...]
addFilters: function(filters) {
    for(filter in filters) {
        this.filters[filter] = filters[filter] ;
        with({ _filter: filter }) // with this you create a new scope with the local variable `_filter` holding the value of `filter`
            this.Image.prototype[filter] = function(image, arguments){ image.addQueue(_filter, arguments) } ; // and this closure is bound to the new scope
    }
},
[...]

Переменные image и arguments являются параметрами закрытия, поэтому вам не нужно добавлять их в новую область. Вы передаете это значение после фактического выполнения закрытия.

Кроме того, вам не нужно называть переменную _filter по-другому. Вы можете просто назвать его filter, и он просто затенит filter внешней области видимости.

person GetFree    schedule 10.04.2011
comment
Интересный подход, но я слышал, что функциональность with() несовместима в разных браузерах. - person ; 12.04.2011
comment
Я этого не слышал. Я делал это много раз для общедоступных веб-сайтов, и это всегда работало нормально. - person GetFree; 13.04.2011