In the code below I'm adding a callback function essentially to a list in a loop.
every Item in the list that I call the call back it will log >>item30
can you please tell me why? Is there a way create a new function() as the call back so
that it can log
item0
item1
item2
item3
item4
and so on .......
......
for (var j = 0 ; j < 30 ; j += 1) {
addThisThingtoList(function () {
console.log( "item" +j );
});
}
This only happens if your function addThisThingtoList() is using asynchronous behavior (like Ajax calls) and thus its callback is called some time later after the for loop has completely run its course and thus it's index value is at the ending value.
You can fix that with a closure that will freeze the loop value separately for each call to addThisThingtoList() like this:
for (var j = 0 ; j < 30 ; j += 1) {
(function(index) {
addThisThingtoList(function () {
console.log( "item" + index);
});
})(j);
}
Working demo: http://jsfiddle.net/jfriend00/A5cJG/
By way of explanation, this is an IIFE (immediately invoked function expression). The variable j is passed to the IIFE and it becomes a named argument to the function that I named index. Then, inside that function, you can refer to index as the value of j that is frozen uniquely and separately for each call to addThisThingtoList(). I could have named the argument index to also be j in which case it would have just overriden the higher scoped j, but I prefer to use a separate variable name to be more clear about what is what.
Here's a good reference on the IIFE concept if you want to read more about it:
http://benalman.com/news/2010/11/immediately-invoked-function-expression/
This is what closure is all about.
When you call the function, it pulls the value of j at the time it's called, not at the time when it was added to the list.
So by the time you start calling the functions, j is already at 30.
It's a classic javascript scope bug. You need to pass in the j variable to your function:
addThisThingToList(function(j) {
console.log("item" + j);
});
As #Smeegs says, "this is what closure is all about."
Related
I have a function -- let's call it test(arg1,arg2), called from program1, which does a number of things and is working correctly. Within test there is a loop:
for(j=1;j<=top;j++) {
stuff happens based on j
}
I would like to call test(arg1,arg2) from a different program, say program2. Everything about test is the same for these two programs except the for loop. For program2 I need that loop to be
for(j=2;j<=top;j+=2) {
stuff happens based on j
}
Otherwise everything else is exactly the same.
The second argument, arg2 tells us whether the script was called from program1 or program2. But I can't figure out how to write a variable "for" statement. I tried an if statement based on arg2
var jstart = 1 or 2
var jincr = '++' or '+=2'
and then wrote the loop as
for(j=jstart;j<=top;j jincr) {
This did not work, although it is an approach that works in other languages.
Can someone suggest I way I can do this without writing an entirely separate script for the two cases?
As simple as that
jstart = 1 // or 2
jincr = 1 // or 2;
for(j=jstart;j<=top;j += jincr) {
The most reusable way would be to put your loop in a function that accepts increment as an argument:
function doStuff (inc) {
for(var j = inc; j <= top; j += inc) {
// stuff happens based on j
}
}
// Program 1
doStuff(1)
// Program 2
doStuff(2)
What is the main difference between:
var test=[5,6,7,8,9];
$.each(test, function (i, num) {
setTimeout(function(){console.log('i: '+i+' '+num)},500);
});
for (var j = 0; j < test.length; j++) {
setTimeout(function(){console.log('j: '+j+' '+test[j])},500);
}
First loop outputs:
i: 0 5
i: 1 6
i: 2 7
i: 3 8
i: 4 9
Second loop outputs:
5 times 'j: 5 undefined'
I understand why the second loop does this, but not why $.each works 'as expected'
Thanks!
In the first snippet's behind the scene, for every iteration the call back that you have supplied will be called. That means, internally it will create a scope per iteration. That eventually becomes a cloure.
But in the second snippet. The initial scope will be used while the timeout's call back got triggered. So it is displaying that output. And you can make it work by using the following code,
for (var j = 0;j<test.length-1;j++) {
var scope = function(i) {
setTimeout(function(){console.log('j: '+i+' '+test[i])},500);
};
scope(j);
}
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I have this for loop:
for (var i = 1; i < array.length; i++) {
$('#shop'+i).on('click', function(){ buyThis(i) });
}
The problem is that the i passed as argument to buyThis() isn't equal to the i.
array.length is equal to 3 and #shop1 and #shop2 are created but the argument passed to buyThis() is 3. Why is that?
I'm trying to get an event in which clicking on #shop1 triggers buyThis(1) and clicking on #shop2 triggers buyThis(2).
That's because the code inside the function is called later, when the loop has finished and the variable i has reached its final value.
You can wrap the code that sets the event in an immediately invoked function expression (IIFE) to create a variable for each iteration of the loop:
for (var i = 1; i < array.length; i++) {
(function(i){
$('#shop' + i).on('click', function(){ buyThis(i); });
})(i);
}
This is a scope issue. You need to create a proper closure
for (var i = 1; i < array.length; i++) {
(function(index){
$('#shop'+index).on('click', function(){ buyThis(index) });
})(i);
}
The reason it was different before is because in javascript. the variable is still visible to the handler and by the time it is accessed, it is set to the latest iteration.
This is an issue with block scoping, you can use a closure to solve this
for (var i = 1; i < array.length; i++) {
(function(index){
$('#shop'+index).on('click', function(){ buyThis(index) });
})(i);
}
I am trying to make a cell in a table tell me its number when I click it.
for(var j = 0; j < 8; j++){
var cell = row.insertCell(j);
cell.name = j;
cell.onclick=function(){alert(cell.name)};
}
This however, prints the number 8 for every cell. How do I save the value of j in cell.name, instead of just having it point to the variable j?
Thanks.
IMPORTANT: All JavaScript developers should know this. It will cause all kinds of weird bugs that is very hard to find.
It is a common mistake of people who are new to JavaScript. I've made the same mistake before.
A function inside a loop is NOT created for every iteration. It is the same one function object with the same closure scope. Thus, your cells will have the exact same onclick callback.
My advice here is NEVER EVER create a function inside of loop. Instead, create and call a function that returns a callback function and assign it to onclick.
for (var j = 0; j < 8; j++) {
var cell = row.insertCell(j);
cell.name = j;
cell.onclick = createOnClick(cell);
}
function createOnClick(cell) {
return function () {
// do whatever you want to do with cell
};
}
Below is the code I'm working on. I'm wondering why, when I change the variable ttt in the function, the changes do not stay outside of the function? I've declared it as var ttt = new Array; at the very top.
Also, why can't I use the variable i in the function?
code:
client.on('connection', function()
{
var sss;
var aaa;
console.log('Connected');
for (i = 0 ; i < 120 ; i++)
ttt[i] = 0;
for (i = 0 ; i < 9 ; i++)
{
client.getMatchHistory(434582, function(err, result)
{
sss = JSON.stringify(result);
var myObject = eval('(' + sss + ')');
console.log (myObject.object.data[i].object.value);
ttt[myObject.object.data[i].object.value]++;
});
}
for (i = 0 ; i < 120 ; i++)
console.log ("Record" + i + " Successes: " + ttt[i]);
});
As you pointed out, there are two separate problems with your code, and they're both somewhat related. First, ttt is being modified globally. The problem is that you're checking for the modifications before they happen. I suspect that client.getMatchHistory() is making an asynchronous call. There's no guarantee that all the asynchronous operations in the second for loop will be done executing by the time you execute the third for loop, where you read the array.
The second problem is one of scope, but it's not global scope that's your problem. Since client.getMatchHistory() is asynchronous, the callbacks will be called once the loop is done executing. Once the loop's done executing i will have a value of 10. Likely this is not what you intended. You need to create a callback generating function, that takes the value of i, and returns a function that can be used as a callback. Like this:
function make_callback(i) {
return function(err, result) {
// The body of your callback in here
};
}
And then you should use it like this in the body of the loop:
client.getMatchHistory(434582, make_callback(i))
This will capture the value of i in the current iteration and the generated callback will use that value when executed. That should fix your problem with i.
First of all, all globals variables are effectively 'window' object fields, so you can use window.ttt to be sure you are using global variables instead of local. This code should work, so did you try it in developer tool? what does debugger say about presence of such variable?
As for variable i: sure, you can use it, but it better to use it locally, defining 'var i;' on the top of the function to not spoil global namespace.
The client.getMatchHistory probably asynchronous request, you expect that after loop, you will have filled ttt array, to acheive this you have to make a handler which run after last loop step:
var afterloop=function() {
for (var i = 0 ; i < 120 ; i++)
console.log ("Record" + i + " Successes: " + ttt[i]);
}
for (var i = 0 ; i < 120 ; i++)
ttt[i] = 0;
var i_final=0;
for (var i = 0 ; i < 9 ; i++)
{
var i=i; //localise i
client.getMatchHistory(434582, function(err, result)
{
i_final++;
sss = JSON.stringify(result);
var myObject = eval('(' + sss + ')');
console.log (myObject.object.data[i].object.value);
ttt[myObject.object.data[i].object.value]++;
if (i_final>8) {afterloop();}
});
}
in the sample, i_final counts done requests, they can be done in random order, due to async, so you can't refer to i when deciding to run afterloop() , when i_final count to more than last executed request, you run function that should be executed after last request is done.
Note: please use global vars as less as possible, in your code you used global i without any reason