0

I have an array of strings in Javascript that I need to use to load images in my page via AJAX. After each image is loaded, I need to perform some additional tasks such as sending a HTTP request which will actually delete the image.

This is what I have so far.

for (x in images) {
    var img = new Image();
    $(img)
        .load(function(){
            $('#image_'+images[x]).html(''); //remove loading gif
            $('#image_'+images[x]).append(this);
            $.post('/images/destroy_image',{id:images[x]});
        })
        .attr('src', '/images/get_image?id='+images[x]);
}

This code works fine if there is only 1 string in the images array.

If there is more than 1, when an image finishes loading and its load function is run, any reference to images[x] in its load function now point to the final string in the images array, when I need it to be the value it was when the loop was being run.

Eg.

images is {'12','100'}

When the first image finishes loading, the first line in its load function will be run as

$('#image_100').html('');

when it should be

$('#image_12').html('');

How can I do this?

3
  • Fyi, you can use $('<img/>') instead of the new Image() stuff. Commented Jul 10, 2011 at 10:18
  • images is {'12','100'}. That would result in a syntax error. I hope you mean ['12','100']. Commented Jul 10, 2011 at 10:27
  • This question is similar to: JavaScript closure inside loops – simple practical example. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented Oct 8, 2024 at 17:52

4 Answers 4

3

Typical function-in-a-loop problem. You have to "capture" the current value of x by introducing a new scope (through a function, does not have block scope). You could do:

function loadImage(image) {
    $('<img />')
        .load(function(){
            $('#image_'+image).html(''); //remove loading gif
            $('#image_'+image).append(this);
            $.post('/images/destroy_image',{id:image]});
        })
        .attr('src', '/images/get_image?id='+image);
}

for (var i = 0, l = images.length; i < l; i++) { // it seems `images` is an array 
    loadImage(images[i]);
}

Don't use a for...in loop to iterate over arrays.

Sign up to request clarification or add additional context in comments.

6 Comments

JavaScript also has global scope, try-catch scope and eval scope.
@katspaugh: global scope: yeah kind of. It's just properties of the global object (it's not really "scope"). try-catch scope: What do you mean exactly? How does this introduce scope? eval scope: No, eval is evaluated in the current scope.
catch takes an argument, which is local to the catch block. eval I don't understand as clearly. I was referring to the eval oddities described in dmitrysoshnikov.com/ecmascript/es5-chapter-2-strict-mode/…
@katspaugh: Regarding catch: yes, the argument is indeed local to the block, but if you define a "local" variable inside the catch block (var x = ...), then this one is local to the outer function, not to that block. Regarding eval: I don't have the time to read all this... maybe later ;)
It basically says that there's "indirect eval call", like (1, eval('...')) which executes in the global scope.
|
2

jquery's each() function closes over each value in the array, so you don't end up with a reference to just the last value:

$.each(images, function(i,v) {
    var img = new Image();
    $(img)
        .load(function(){
            $('#image_'+v).html(''); //remove loading gif
            $('#image_'+v).append(this);
            $.post('/images/destroy_image',{id:v});
        })
        .attr('src', '/images/get_image?id='+v);
});

Comments

0

Try using a traditional for loop, so you can have a counter. I think the "x" in your foreach loop is the actual object and not the index value, but you use it like an index elsewhere. Does that fix it?

3 Comments

No, x is the index (or property name), not the value: developer.mozilla.org/en/JavaScript/Reference/Statements/…
My understanding is that "images" contained an array of numbers, like 12,100,5 etc, so calling images[x] within the loop would be images[12]. I could be wrong, I never was great at loops
Your understanding of images is correct, but x will not be the value. for...in iterates over the properties of an object, which, if it is an array, happens to be the array indexes. Have a look at the link I posted...
0

You're running into this issue due to some semantic discrepancies that exist between the way closures are frequently explained and the way they actually work in most languages. Without getting into that topic, you may have better luck with:

for (var index = 0; index < images.length; index++) {
    var img = new Image();
    $(img)
        .load(load(images[index]))
        .attr('src', '/images/get_image?id='+image);
}

function load(image) {
    return function() {
        $('#image_'+image).html(''); //remove loading gif
        $('#image_'+image).append(this);
        $.post('/images/destroy_image',{id:image});
    };
} 

If you want to get into the topic of why this works where the previous example failed, closures are often explained as creating a snapshot of the state that exists in the current lexical scope. This is a somewhat misleading analogy, because unlike a true snapshot which is immutable, the state inside of the closure continues to live and can be modified by things that happen outside of the closure.

Case in point, when you do something like:

for (x in myArray) {
    setTimeout(function() {
        alert(myArray[x]);
    }, 2000);
}

...there is only a single variable instance referred to by x, and the closure is capturing a reference to x as opposed to a snapshot of the current value of x. Thus when subsequent iterations modify the value of the single instance of x that exists in this scope, the change appears in every closure that was created with a reference to x.

And it works when you do:

for (x in myArray) {
    var item = myArray[x];
    setTimeout(makeClosure(item), 2000);
}

function makeClosure(value) {
    return function() {
        alert(value);
    };
}

...because a different variable reference is created on each iteration (by calling makeClosure()), and once created the value is never modified. Each closure that is created carries a reference to the most recently defined value, and thus appears to "remember" the correct value later on when it executes.

4 Comments

This will not help either. By the time the load callback runs, image will have the value of the last iteration. JS does not have block scope (so it's like declaring image before the loop).
because a different item variable is created on each iteration no, it is not (not in JavaScript) and that's why it does not work.
@Felix - Yes, it's fixed now. A bit surprised about there being no lexical scope for the block, but jsfiddle confirmed that quickly enough.
Yes, that is one of JavaScript's quirks. All variable and function declarations are hoisted to the beginning of the containing scope... and blocks do not create a new scope.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.