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