Присвояване на затваряния в цикли към .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