4

有时 JavaScript 对我来说没有意义,请考虑以下代码,该代码基于 x/y 平铺生成照片马赛克。下载每个马赛克图像后,我正在尝试将 .Done 属性设置为 true,但由于某种原因它总是错误的,我做错了什么?

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

现在由于某种原因,对象.Done上的属性tile始终为 false,即使它在内部被明确设置为 true tileImage.onload = function()。而且我可以确保您确实调用了此函数,因为我已在alert()其中进行了调用。现在它只是被困在一个tryDisplayMosaic()不断调用的无限循环中。

此外,如果我在调用之前放置一个循环tryDisplayMosaic(),并在该循环中设置.Done = true,则.Done属性为 truealert('All images downloaded');并将被调用。

4

2 回答 2

2

顶部循环中的变量“tile”由您创建的每个“onload”函数共享;换句话说,同一个变量。试试这个,给每个人自己的变量:

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 回答
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 回答