Come risolvere il problema di chiusura in ActionScript 3 (AS3)

Nel codice qui sotto sto cercando di caricare alcune immagini e metterle sul palco non appena vengono caricate singolarmente. Ma è bloccato dal momento che viene visualizzata solo l’ultima immagine. Sospetto che sia un problema di chiusura. Come posso ripararlo? Il comportamento delle chiusure in AS3 non è lo stesso di 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)); } 

Il comportamento delle chiusure in AS3 non è lo stesso di Java Script?

Sì, JavaScript fa esattamente la stessa cosa. Come fa Python. E altri.

Sebbene tu definisca “var imageData” all’interno di “for”, i loop for non introducono un nuovo ambito in questi linguaggi; infatti la variabile imageData è associata all’ambito di contenimento (la funzione esterna, o in questo caso sembra essere un ambito globale). Puoi verificare ciò osservando imageData dopo che il ciclo ha completato l’esecuzione e trovando l’ultimo elemento di imageList al suo interno.

Quindi esiste una sola variabile imageData, non una per ogni iterazione del ciclo. Quando COMPLETE si triggers, entra in chiusura e legge qualunque valore imageData abbia ora , non al momento in cui la funzione è stata definita (*). Tipicamente il ciclo for avrà finito con il punto COMPLETE che si triggers e imageData manterrà quell’ultimo elemento dall’iterazione finale.

(* – esistono linguaggi ‘early-binding’ che valuteranno il valore della variabile nel punto in cui definisci una chiusura, ma ActionScript non è uno di questi.)

Le possibili soluzioni tendono a implicare l’uso di una funzione esterna per introdurre un nuovo ambito. Per esempio:

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

È ansible / mettere questo in linea, ma la funzione doppiamente annidata () inizia a essere più difficile da leggere.

Vedi anche Function.bind () per una funzione di applicazione di funzioni parziali di uso generale che potresti usare per ottenere questo. È probabile che faccia parte delle future versioni di JavaScript / ActionScript e, nel frattempo, possa essere aggiunto al linguaggio tramite la prototipazione.

Utilizzando lo stile più funzionale per il metodo Any nella class Array si evita questo problema. Questo è già stato menzionato ma lo espanderò qui.

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

Utilizzando questo metodo, la funzione passata in forEach definisce un nuovo ambito ogni iterazione. ora puoi aggiungere una chiusura all’interno di questo ambito e ricorderà ogni istanza come desideri.

Su una nota correlata:

Digitando questi 3 argomenti tutto il tempo è un dolore così … Puoi anche renderlo meno / più brutto con una funzione adattatore:

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

dove itrAdpt è una funzione (possibilmente globale) definita qualcosa come:

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

Se la creazione di una funzione con nome non ti piace, la risposta di Bobince può essere convertita a questo senza molto sacrificio alla leggibilità:

 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 la mia preferenza, il tuo chilometraggio può variare.