This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
Im working with the gm npm module that deals with image manipulation. and i have this code.
for(i=0;i < 4;i++){
gm("www/img/" + image[i]).crop(550, 406, 0, 0).write(function(err) {
console.log(this.outname + " created :: " + arguments[3]); //success
});
}
this loop is meant to loop through the images array and crop each photo, but it only crops the last one. i think its something to do function invocation and callbacks, but not advanced yet for that level.
Change your code to:
for (var i = 0; i < 4; i++) {
(function (i) {
gm("www/img/" + image[i]).crop(550, 406, 0, 0).write(function(err) {
console.log(this.outname + " created :: " + arguments[3]); //success
});
}).call(this, i);
}
otherwise the value of i will be 3 each time your callback is being invoked.
You need to create a "closure" over the variable
Js has a function scope.
for (i = 0; i < 4; i++)
{
(function (a)
{
gm("www/img/" + image[a]).crop(550, 406, 0, 0).write(function (err)
{
console.log(this.outname + " created :: " + arguments[3]); //success
});
}).call(this,i)
}
or
that=this;
for (i = 0; i < 4; i++)
{
(function (a)
{
gm("www/img/" + image[a]).crop(550, 406, 0, 0).write(function (err)
{
console.log(that.outname + " created :: " + arguments[3]); //success
});
})(i)
}
edit :
Also - I would also keep a reference to the arguments since now , after IIFE - the arguments is changing.
you can keep your arguments via :
var args= Array.prototype.slice.apply(arguments)
example :
function g()
{
for (i = 0; i < 4; i++)
{
(function (a)
{
console.log(arguments); //huh ? arguments are not a,b,c !!! anymore
})(i);
}
}
g('a','b','c') // 0,1,2,3
so you do need to keep reference to the arguments cuz their changed after IIFE.
Related
I am trying to test some code and for that I need to make some tests in loop, like this:
for (var i = 1; i <= 5; i++) {
QUnit.test('Hello ' + i, (assert) => {
console.log(i);
assert.ok( 1 == '1', 'Result: ' + i);
});
}
Click here for working example at jsFiddle
But for some reason i in the loop (and result) is always 6, so this code gives me output like this:
6
6
6
6
6
What am I doing wrong?
Given that QUnit defines all tests prior to running them, you're a victim of the classic problem of var scope - vars are bound to the function, not to the for loop.
What this means is:
You define your test with a given value for i, but this value will have changed when the test is actually running.
You have a few ways around this:
Create an IIFE and define your test inside it
for (var i = 1; i <= 5; i++) {
(function (j) {
QUnit.test('Hello ' + j, (assert) => {
console.log(j);
assert.ok( 1 == '1', 'Result: ' + j);
});
})(i);
}
Why this works: the j variable above will be bound to the scope of that IIFE. Its value will not change when the test is run.
Use let keyword in an ES6 environment
for (let i = 1; i <= 5; i++) {
QUnit.test('Hello ' + i, (assert) => {
console.log(i);
assert.ok( 1 == '1', 'Result: ' + i);
});
}
Why this works: ES6 finally introduces block scoping, but this is done via the keywords let or const.
some parts of this answer corroborated from here
This question already has answers here:
Javascript infamous Loop issue? [duplicate]
(5 answers)
Closed 9 years ago.
for (var i = 0; i < 10; i++) {
array.push($("" + i + ""));
$("#row").append(array[i]);
array[i].click(function () {
changeval(i);
console.log(i);
});
}
My problem is that the function changeval(i) becomes always the same value 10 of i.
I try to create buttons with onclick-function in this for-loop. I need a hint.
I would separate the initial adding of the buttons and the 'click' action.
for (var i = 0; i < 10; i++) {
array
.push($("<a href=\"#\" data-role=\"button\" data-inline=\"true\">"
+ i + "</a>"));
$("#row").append(array[i]);
array[i].click(function() {
changeval(i);
console.log(i);
});
}
Would be split up to:
for (var i = 0; i < 10; i++) {
array.push("<a href=\"#\" data-role=\"button\" data-inline=\"true\">"
+ i + "</a>");
$("#row").append(array[i]);
};
$('#row').on('click', 'a', function() {
changeVal($(this).text());
console.log($(this).text());
};
Also note that variables and functions within javascript should be written in CamelCaseForBetterReadability. Also note that I got rid of the $() surrounding the array items. Lastly, if you do not want to escape the quotations within your string, you can use a single quotation.
Look into JS closures, the value of i is set to the last iteration of the loop. Wrap that in a self-executing func with i as the param:
for (var i = 0; i < 10; i++) {
(function(i) {
array
.push($("<a href=\"#\" data-role=\"button\" data-inline=\"true\">"
+ i + "</a>"));
$("#row").append(array[i]);
array[i].click(function() {
changeval(i);
console.log(i);
});
})(i)
}
Use a closure, e.g.
for (var i = 0; i < 10; i++) {
(function(i) {
array[i].click(function() { . . . } );
)(i);
}
If you're using jQuery (seems like it if you're using .click :) you can use
http://api.jquery.com/jQuery.each/
to loop through elements and acheive this same functionality.
I have received this comment:
On the server side of Node.js when handling incoming data if you want to use a for loop you have to create i inside an anonymous function or you will pull your hair out wondering how the hell your variable i is greater than what you limit it to be inside your loop.
Here is the bugfix that was recommended:
var i = 0,
len = that.users.length;
(function(i) {
while(i < len) {
console.log(' - - - - - debug - - - - -');
console.log('i = ' + i );
i++;
}
})(i);
Can someone explain to me why using an anonymous function is necessary?
The problem only shows up when you have asynchronous code inside the loop. For example (I changed to a for loop for simplicity):
var i = 0,
len = that.users.length;
for(i = 0;i < len;i++) {
setTimeout(function() {
console.log('i = ' + i );
}, 500);
}
You will find that running this code causes the value of len to be printed len times, instead of a count up to len. This is because the for loop finishes before any of the print statements run, so the loop has exited because i == len.
The fix for this is to lock i in to each value with an Immediately-Invoked Function Expression (IIFE):
var i = 0,
len = that.users.length;
for(i = 0;i < len;i++) {
(function(i) {
setTimeout(function() {
console.log('i = ' + i );
}, 500);
})(i);
}
This isn't exactly the bugfix you presented, but it is the closest I can think of that makes much sense. Given more context, I could be more sure about what problem it is supposed to solve.
I have a question to a specific behavior of javascript:
I have an object which I want to fill with generated functions. Each function contains a variable which is changed during the loop of function generation.
My problem is that the variable does not get replaced when assigning the function to the object. Instead the reference to the variable stays in the function and when executing the function only the last value of the variable is remembered.
Here is a minimal example (also on jsfiddle: http://jsfiddle.net/2FN6K/):
var obj = {};
for (var i = 0; i < 10; i++){
var nr = i;
obj[i] = function(){
console.log("result: " + nr);
}
}
for (var x = 0; x < 10; x++){
obj[x]();
}
The second loop executes all generated functions and all print a 9 as result. But i want that they print the value which the variable had at the time of generation (0, 1, 2, ...).
Is there a way to do this? Thanks in advance.
One approach is to call a function that returns a function:
function makeFunc(i) {
return function() {
console.log("result: " + i);
}
}
for (...) {
obj[i] = makeFunc(i);
}
Another approach is the immediately invoked function expression:
for (i = 0; ...; ...) {
(function(i) {
obj[i] = function() {
console.log("result: " + i);
}
})(i);
}
where in the latter case the (function(i) ... )(i) results in a permanent binding of i passed as a parameter to the outer function within the scope of the inner function
The problem is that all the functions you create are sharing a reference to the same nr variable. When you call them they fetch the value using that reference and therefore all of them get the same result.
Solve it like this:
for (var i = 0; i < 10; i++){
(function(nr) {
obj[nr] = function(){
console.log("result: " + nr);
}
})(i);
}
Your surmise is correct, and yes, there's a solution:
obj[i] = function( nr_copy ) {
return function() {
console.log("result: " + nr_copy);
};
}( nr );
JavaScript variables are scoped at the function level, unlike some other block-structured languages. That is, the fact that you declare "nr" inside the for loop doesn't make it "local" to that block — the effect is precisely the same as if you'd declared it at the top of the function.
By introducing another function scope with that anonymous function, you make a new copy of the value of "nr", which is then privately accessible to the actual function that's returned. Each of those functions will have it's own copy of the value of "nr" as it stood when that slot of the "obj" array was initialized.
what you want is to create a closure for every function you create.
Yet, the var(s) have not a block scope, so your code is the same as :
var obj = {};
var i;
var nr;
for (i = 0; i < 10; i++){
nr = i;
obj[i] = function(){
console.log("result: " + nr);
}
}
which hopefully makes it more obvious all functions will refer to the very same 'nr' var.
What you want to do implies creating a new scope each time, which might be done using bind, but let's stick to your original intent and build a new closure each time with a lambda :
var obj = {};
for (var i = 0; i < 10; i++) {
obj[i] = (function(){
var this_nr = i;
return function(){
console.log("result: " + this_nr);
}
}() );
}
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I am trying to execute the following code:
for (var i = 0; i <= 9; ++i) {
State.prototype["button" + i.toString()] = function () {
console.log("I am a digit button" + i.toString());
this.setValue(i.toString());
};
}
But it is wrong, because the i variable is common for all the function created.
For example I want the function State.prototype.button0() to work as:
console.log("I am a digit button" + "0");
this.setValue("0");
How to do it?
Pass it to a function, so that the value of i doesn't change:
for (var i = 0; i <= 9; ++i) {
(function(i){
State.prototype["button" + i.toString()] = function () {
console.log("I am a digit button" + i.toString());
this.setValue(i.toString());
};
})(i);
}