4

JavaScript が意味をなさない場合があります。x/y タイルに基づいて写真のモザイクを生成する次のコードを検討してください。各モザイク画像がダウンロードされたら、.Done プロパティを true に設定しようとしていますが、何らかの理由で常に false になっています。何が間違っていますか?

var tileData = [];

function generate()
{
    var image = new Image();
    image.onload = function()
    {
        // Build up the 'tileData' array with tile objects from this Image

        for (var i = 0; i < tileData.length; i++)
        {
            var tile = tileData[i];

            var tileImage = new Image();
            tileImage.onload = function()
            {
                // Do something with this tile Image
                tile.Done = true;
            };
            tileImage.src = tile.ImageUrl;
        }
    };
    image.src = 'Penguins.jpg';

    tryDisplayMosaic();
}

function tryDisplayMosaic()
{
    setTimeout(function()
    {
        for (var i = 0; i < tileData.length; i++)
        {
            var tile = tileData[i];

            if (!tile.Done)
            {
                tryDisplayMosaic();
                return;
            }
        }

        // If we get here then all the tiles have been downloaded
        alert('All images downloaded');
    }, 2000);
}

内部で明示的に true に設定されていても、何らかの理由.Doneで、オブジェクトのプロパティtileは常に false ですtileImage.onload = function()alert()そして、この関数内に呼び出しを配置し​​たため、この関数が呼び出されることを確認できます。tryDisplayMosaic()現在、常に呼び出しを行う無限ループに陥っています。

また、 が呼び出される直前にループを配置tryDisplayMosaic()し、そのループで を設定する.Done = trueと、.Doneプロパティは true にalert('All images downloaded');なり、呼び出されます。

4

2 に答える 2

2

私が見ているのは、ループ内にクロージャーを作成することです。これはよくある間違いです。これを回避するには、ループの外側に、すべてのロード イベントを処理し、すべての画像がロードされた後に関数を実行する、より一般的なクロージャを作成できます。

// This array must be filled somewhere...
var tileData = [];

var ImageLoader = function(){
    var numOfImages = 0;
    var numLoaded   = 0;
    var callBack    = function(){};

    function imageLoaded(){
        numLoaded++;
        if(numLoaded===numOfImages){
            // All images are loaded, now call the callback function
            callBack.call(this);
        }
    }

    function init(numberOfImages, fn){
        numOfImages = numberOfImages;
        callBack = fn;
    }

    return {
        imageLoaded: imageLoaded,
        init: init
    };
}();

function tryDisplayMosaic(){
    alert('All images downloaded');
}

function generate(){
    // (1) Set the number of images that are to be loaded and (all tiles + penguin)
    // (2) Set the function that must be called after all images are loaded
    ImageLoader.init(tileData.length+1, tryDisplayMosaic);

    // Load this Penguins image
    var image = new Image();
    image.src = 'Penguins.jpg';
    image.onload = ImageLoader.imageLoaded;

    // Go through each image and load it
    for (var i=0; i<tileData.length; i++) {
        image = new Image();
        image.src = tileData[i].ImageUrl;
        image.onload = ImageLoader.imageLoaded;
    }
}

このコードはあなたのコードとはまったく異なりますが、私の意見でははるかにきれいです。ロードする必要がある画像の数と、すべての画像がロードされた後に実行する必要がある関数を設定する必要があります。

注:私はそれをテストしていません。この.call(this)部分についてはわかりません。間違っている場合はフォーラムが役立つかもしれません。

コードをテストしましたが、正常に動作します。さらに、私はそれを改善したので、ロードされた画像へのポインターをtileData配列内に保存します。実際の例については、 JSBinを参照してください。

于 2010-05-09T16:10:54.380 に答える
2

一番上のループの変数「タイル」は、作成したすべての「オンロード」関数によって共有されます。つまり、まったく同じ単一の変数です。これを試して、それぞれに独自の変数を与えてください:

tileImage.onload = (function(myTile) {
  return function()
        {
            // Do something with this tile Image
            myTile.Done = true;
        };
})(tile);

これはなぜですか?C や Java とは異なり、そのようにループ内で「タイル」を宣言しても、ループ本体のステートメント ブロックにスコープが設定されないためです。Javascript でスコープを提供する唯一のものは、関数本体です。

于 2010-05-09T15:59:27.330 に答える