Подтвердить что ты не робот

Как исправить проблему закрытия в ActionScript 3 (AS3)

В приведенном ниже коде я пытаюсь загрузить некоторые изображения и поместить их на сцену, как только они будут загружены индивидуально. Но он прослушивается, так как отображается только последнее изображение. Я подозреваю, что это проблема закрытия. Как я могу это исправить? Разве поведение закрытий в AS3 не такое же, как в Java Script?

var imageList:Array = new Array();
imageList.push({'src':'image1.jpg'});
imageList.push({'src':'image2.jpg'});
var imagePanel:MovieClip = new MovieClip();
this.addChildAt(imagePanel, 0);

for (var i in imageList) {
    var imageData = imageList[i];
    imageData.loader = new Loader();

    imageData.loader.contentLoaderInfo.addEventListener(
        Event.COMPLETE, 
        function() {
            imagePanel.addChild(imageData.loader.content as Bitmap);
            trace('Completed: ' + imageData.src);             
        });

    trace('Starting: ' + imageData.src);
    imageData.loader.load(new URLRequest(imageData.src));   
}
4b9b3361

Ответ 1

Не является ли поведение замыканий в AS3 таким же, как в Java Script?

Да, JavaScript делает то же самое. Как и Python. И другие.

Хотя вы определяете "var imageData" внутри "for", для циклов не вводите новую область на этих языках; на самом деле переменная imageData связана в области содержимого (внешняя функция, или в этом случае она представляется глобальной областью). Вы можете проверить это, посмотрев на imageData после того, как цикл завершил выполнение, и найдя в нем последний элемент imageList.

Таким образом, существует только одна переменная imageData, а не одна для каждой итерации цикла. Когда COMPLETE срабатывает, он входит в закрытие и считывает любое значение, которое имеет значение imageData, а не в то время, когда функция была определена (*). Как правило, цикл for будет завершен точкой COMPLETE, а imageData будет удерживать последний элемент из последней итерации.

(* - существуют языки раннего связывания, которые будут оценивать значение переменной в точке, которую вы определяете закрытием. Но ActionScript не является одним из них.)

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

function makeCallback(imageData) { return function() {
    imagePanel.addChild(imageData.loader.content as Bitmap);
    trace('Completed: ' + imageData.src);                                                                                                     
} }
...
imageData.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, makeCallback(imageData));

Вы/можете/поместить этот встроенный, но дважды вложенная функция() начинает усложняться.

См. также функцию Function.bind() для функции приложения частичных функций общего назначения, которую вы можете использовать для достижения этой цели. Он, вероятно, будет частью будущих версий JavaScript/ActionScript и может быть добавлен к языку через прототипирование тем временем.

Ответ 2

Использование более функционального стиля для каждого метода в классе Array позволяет избежать этой проблемы. Это уже упоминалось, но я расскажу об этом здесь.

imageList.forEach( function ( item:MovieClip, index:int, list:Array) {
    // add your listener with closure here
})

Используя этот метод, функция, переданная в forEach, определяет новую область для каждой итерации. теперь вы можете добавить закрытие внутри этой области, и он будет помнить каждый экземпляр, как вы хотите.

В соответствующей заметке:

Набрав эти 3 аргумента, все время это боль, поэтому... Вы также можете сделать это менее/уродливым с помощью функции адаптера:

// just give me the item please
imageList.forEach ( itrAdpt( function ( image: ImageData ) {
    // add your listener with closure here
}))

// give me the item and it index
imageList.forEach ( itrAdpt( function ( image: ImageData, index:int ) {
    // add your listener with closure here
}))

// give me the item, index and total list length
imageList.forEach ( itrAdpt( function ( image: ImageData, index:int, length:int ) {
    // add your listener with closure here
}))

где itrAdpt - (возможно глобальная) функция, определенная как:

public function itrAdpt(f: Function): Function
{
    var argAmount:int = f.length

    if (argAmount == 0)
    {
        return function (element:*, index:int, colection:*):* {
            return f(element)
        }
    }
    else if (argAmount == 1)
    {
        return function (element:*, index:int, colection:*):* {
            return f(element)
        }
    }
    else if (argAmount == 2)
    {
        return function (element:*, index:int, colection:*):* {
            return f(element, index)
        }
    }
    else if (argAmount == 3)
    {
        return function (element:*, index:int, colection:*):* {
            return f(element, index, colection.length)
        }
    }
    else
    {
        throw new Error("Don't know what to do with "+argAmount+"arguments. Supplied function should have between 1-3 arguments")
    }
}

Ответ 3

(function() {
  var imageData = imageList[i];
  imageData.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function() {
    // use imageData;
  });
}).apply();

Ответ 4

Если создание именованной функции не обращается к вам, bobince ответ может быть преобразован в это без большой жертвы для удобочитаемости:

var makeCallback = function(imageData:String) 
  { 
    return function(evt:Event) 
    {
      imagePanel.addChild(imageData.loader.content as Bitmap);
      trace('Completed: ' +  imageData.src);
    } 
  }

...

imageData.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, makeCallback(imageData));

Только мои предпочтения, ваш пробег может отличаться.