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?
4 Answers 4
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]);
}
6 Comments
try-catch scope and eval 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/… 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 ;)eval call", like (1, eval('...')) which executes in the global scope.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
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
x is the index (or property name), not the value: developer.mozilla.org/en/JavaScript/Reference/Statements/… 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...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
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).item variable is created on each iteration no, it is not (not in JavaScript) and that's why it does not work.
$('<img/>')instead of thenew Image()stuff.imagesis{'12','100'}. That would result in a syntax error. I hope you mean['12','100'].