如何解决ActionScript 3(AS3)中的闭包问题

时间:2009-01-08 00:37:02

标签: flash flex actionscript-3 closures

在下面的代码中,我试图加载一些图像,并在它们单独加载后立即将它们放入舞台。但它被窃听,因为只显示最后一张图像。我怀疑这是一个关闭问题。我该如何解决? 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 个答案:

答案 0 :(得分:45)

  

AS3中闭包的行为与Java Script中的行为不一样吗?

是的,JavaScript完全一样。和Python一样。还有其他人。

虽然在'for'中定义'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版本的一部分,并且可以在此期间通过原型设计添加到语言中。

答案 1 :(得分:3)

在Array类上使用更多功能样式的forEach方法可以避免此问题。 这已经提到了,但我会在这里进行扩展。

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

使用此方法,传递给forEach的函数在每次迭代时定义一个新范围。现在你可以在这个范围内添加一个闭包,它会记住你想要的每个实例。

在相关说明中:

一直输入这三个参数是一件痛苦的事情...... 您还可以使用适配器功能使其更少/更丑:

// 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")
    }
}

答案 2 :(得分:1)

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

答案 3 :(得分:1)

如果创建一个命名函数对您没有吸引力,那么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));

只是我的偏好,你的里程可能会有所不同。