Как исправить проблему с закрытием в ActionScript 3 (AS3) - PullRequest
25 голосов
/ 08 января 2009

В приведенном ниже коде я пытаюсь загрузить некоторые изображения и поместить их в сцену, как только они загружаются по отдельности. Но он прослушивается, поскольку отображается только последнее изображение. Я подозреваю, что это проблема закрытия. Как я могу это исправить? Разве поведение замыканий в 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));   
}

Ответы [ 4 ]

45 голосов
/ 08 января 2009

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

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

Хотя вы определяете 'var imageData' внутри 'for', циклы 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 и может быть добавлен к языку посредством прототипирования.

3 голосов
/ 19 октября 2010

Использование более функционального стиля метода forEach в классе 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's 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")
    }
}
1 голос
/ 01 ноября 2010

Если создание именованной функции вам не нравится, ответ Бобинса можно преобразовать в него без особой потери читабельности:

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

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

1 голос
/ 13 октября 2010
(function() {
  var imageData = imageList[i];
  imageData.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function() {
    // use imageData;
  });
}).apply();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...