JCoder JCoder - 22 days ago 6
Javascript Question

Why do I only see the last loaded image using this JavaScript code?

The following code snippet is intended to loop through the array (which contains 4 objects); with each iteration it is supposed to produce the image of a card. After 4 iterations, there should be 4 cards side by each in a row.

Problem: only the final image shows up (i.e. the element of myHeroCards[3].image). The alert box shows, in sequence, with each iteration: "0","1",2".

Related posts refer to problems with using "for loops" to step though an array; but I don't think that's the problem because I can get any given image to show up by replacing

x
with a value of 0-3.

I suspect an interaction with how onload works and how the loop statements are evaluated, but I'm stumped on the details.

function startingCardLayout(){
var nextHeroCard=0;
var x=0;
for (i=0;i<=2;i++){
myHeroCards[x].image.onload = function (){ctx.drawImage(myHeroCards[x].image,(xFirstCol+(x*xColInc)),100,xCardSize,yCardSize);}
alert(x);
x=x+1;
}
}

Answer

You're assigning each of these array elements a function defined here:

function () {
      ctx.drawImage(myHeroCards[x].image,
                   (xFirstCol+(x*xColInc)),
                   100,xCardSize,yCardSize
      );
}

This references the variable x, which is defined outside the function. This means that when you change the variable x inside your for loop, these functions will reference the new value of x rather than the old value. This explains why you only see the very last image: all the images are drawing at the exact same location, and only the last one (the one that's on top) is showing up. It also explains why you're seeing the correct values of x inside the loop and why it works when you hardcode the numbers - inside the loop, everything is fine, but after the loop is finished all the functions you created are sharing the same value of x.

To address this, you can use this technique, which creates a local copy of the variable x:

myHeroCards[x].image.onload = function(x) {
       return function () {
           ctx.drawImage(myHeroCards[x].image,
               (xFirstCol+(x*xColInc)),
               100,xCardSize,yCardSize
           );
       }
} (x);

Here, the variable x inside of the function is a fresh copy of the current value of the variable x outside the function.

Generally speaking, if you're working with loops in JavaScript and want to create a number of functions inside the loop that reference something that changes during the loop (here, x, but more generally, something like the loop counter), you might want to use the doubly-nested-function idiom:

 var thingy = function(values, i, care, about) {
     return function() {
         // Code that uses values I care about
     }
 } (values, i, care, about);