Cómo solucionar un problema de cierre en ActionScript 3 (AS3)

En el siguiente código, bash cargar algunas imágenes y ponerlas en escena tan pronto como se carguen de forma individual. Pero está bloqueado porque solo se muestra la última imagen. Sospecho que es un problema de cierre. ¿Cómo puedo arreglarlo? ¿No es el comportamiento de los cierres en AS3 el mismo que en 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)); } 

¿No es el comportamiento de los cierres en AS3 el mismo que en Java Script?

Sí, JavaScript hace exactamente lo mismo. Como lo hace Python. Y otros.

Aunque defina ‘var imageData’ dentro de ‘for’, para los bucles no introduzca un nuevo scope en estos idiomas; de hecho, la variable imageData está vinculada en el ámbito contenedor (la función externa, o en este caso, parece ser un scope global). Puede verificar esto mirando imageData después de que el ciclo haya terminado de ejecutarse, y encuentre el último elemento de imageList en él.

Entonces, solo hay una variable imageData, no una para cada iteración del ciclo. Cuando se COMPLETE incendios, ingresa al cierre y lee cualquier valor que tenga imageData ahora , no en el momento en que se definió la función (*). Normalmente, el for-loop habrá terminado por el punto COMPLETE fuegos e imageData retendrá ese último elemento de la iteración final.

(* – existen lenguajes de “vinculación temprana” que evaluarán el valor de la variable en el punto en que define un cierre, pero ActionScript no es uno de ellos).

Las posibles soluciones tienden a involucrar el uso de una función externa para introducir un nuevo scope. Por ejemplo:

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

Usted / puede / poner esto en línea, pero la función doblemente anidada () comienza a ser más difícil de leer.

Consulte también Function.bind () para una función de aplicación de función parcial de propósito general que podría usar para lograr esto. Es probable que sea parte de futuras versiones de JavaScript / ActionScript, y se puede agregar al lenguaje a través de prototipos mientras tanto.

Usar el estilo más funcional para cada método en la clase Array evita este problema. Esto ya se ha mencionado, pero lo ampliaré aquí.

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

Usando este método, la función pasada al forEach define un nuevo scope en cada iteración. ahora puede agregar un cierre dentro de este scope y recordará cada instancia como desee.

En una nota relacionada:

Escribir esos 3 argumentos todo el tiempo es un problema, así que … También puedes hacer que sea menos / más feo con una función de adaptador:

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

donde itrAdpt es una función (posiblemente global) definida como algo como:

 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") } } 
 (function() { var imageData = imageList[i]; imageData.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function() { // use imageData; }); }).apply(); 

Si la creación de una función con nombre no le atrae, la respuesta de Bobince se puede convertir a esto sin sacrificar demasiado la legibilidad:

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

Solo mi preferencia, su millaje puede variar.