25

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

4 回答 4

45

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

是的,JavaScript 做同样的事情。和 Python 一样。和别的。

尽管您在“for”中定义了“var imageData”,但 for 循环不会在这些语言中引入新的范围;实际上,变量 imageData 绑定在包含范围内(外部函数,或者在这种情况下,它似乎是全局范围)。您可以通过在循环完成执行后查看 imageData 并在其中找到 imageList 的最后一个元素来验证这一点。

所以只有一个 imageData 变量,而不是循环的每次迭代都有一个。当 COMPLETE 触发时,它进入闭包并读取 imageData now具有的任何值,而不是在定义函数时(*)。通常,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() 开始变得更难阅读。

另请参阅 Function.bind() 以获得可用于实现此目的的通用部分函数应用程序功能。它很可能成为未来 JavaScript/ActionScript 版本的一部分,同时可以通过原型设计添加到语言中。

于 2009-01-08T02:43:50.077 回答
3

在 Array 类上使用更实用的 forEach 方法可以避免这个问题。这已经提到过,但我会在这里展开。

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")
    }
}
于 2010-10-19T19:08:35.717 回答
1
(function() {
  var imageData = imageList[i];
  imageData.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function() {
    // use imageData;
  });
}).apply();
于 2010-10-13T10:19:13.660 回答
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));

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

于 2010-11-01T17:15:25.003 回答