Reputation: 29777
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?
Upvotes: 0
Views: 91
Reputation: 816272
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.
Upvotes: 3
Reputation: 54796
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.
Upvotes: 0
Reputation: 45525
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);
});
Upvotes: 2
Reputation: 2459
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?
Upvotes: 0