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スクリプトの場合と同じではありませんか?

はい、JavaScriptはまったく同じことをします。Pythonもそうです。その他。

'for'内に'varimageData'を定義しますが、forループはこれらの言語で新しいスコープを導入しません。実際、変数imageDataは、包含スコープ(外部関数、またはこの場合はグローバルスコープのように見えます)にバインドされています。これを確認するには、ループの実行が完了した後にimageDataを調べ、その中のimageListの最後の要素を見つけます。

したがって、imageData変数は1つだけであり、ループの反復ごとに1つではありません。COMPLETEが起動すると、クロージャーに入り、関数が定義されたときではなく、imageDataが現在持っている値を読み取ります(*)。通常、forループは、COMPLETEが起動する時点で終了し、imageDataは最後の反復からの最後の要素を保持します。

(*-クロージャを定義した時点で変数の値評価する「アーリーバインディング」言語が存在します。ただし、ActionScriptはその1つではありません。)

考えられる解決策は、新しいスコープを導入するために外部関数を使用することを伴う傾向があります。例えば:

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 に答える