Why is bracket notation not working here? [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I am writing a function to fire a series of click handlers with a given time interval in between.
function play(step){
var buttons = document.querySelectorAll('.age');
buttons[0].click();
for (var i = 1; i < buttons.length; i++){
setTimeout(function(b){
b.click();
}, 300 + (step*i), buttons[i]);
}
}
The above function works as intended, however, the following version which to me seems like it should be equivalent, does not work:
function play(step){
var buttons = document.querySelectorAll('.age');
buttons[0].click();
for (var i = 1; i < buttons.length; i++){
setTimeout(function(b){
console.log(i);
console.log(b);
b[i].click();
}, 300 + (step*i), buttons);
}
}
I was getting the following error:
TypeError: Cannot read property 'click' of undefined
After checking, I find that console.log(i) is printing 6. So, apparently, the attribute access isn't occurring until after the loop is over, which explains the error! But what exactly is going on here? I'm relatively new to javascript, but is this behavior the result of the anonymous function acting as a closure? That doesn't sound like the right explanation to me. Is it because setTimeout is delaying the evaluation of the anonymous function?
ETA:
I did an experiment:
function sleep(ms){
var current = new Date().getTime();
while (current + ms >= new Date().getTime()){};
}
function play(step){
var buttons = document.querySelectorAll('.age');
buttons[0].click();
for (var i = 1; i < buttons.length; i++){
setTimeout(function(b){
console.log(b);
console.log(i);
b[i].click();
}, 300 + (step*i), buttons);
sleep(1000);
}
}
when I run play(200) I get the same error message, so sleep(1000) should be enough time to make sure that the loop hasn't exited before the first timeout is up, no?

It's because variable in for loop is the same in each iteration (same reference) and when setTimeout is running the for loop has ended so the i variable will be last value of the loop, to fix this you can create a closure with i variable:
function play(step){
var buttons = document.querySelectorAll('.age');
buttons[0].click();
for (var i = 1; i < buttons.length; i++){
(function(i) {
setTimeout(function(b){
console.log(i);
console.log(b);
b[i].click();
}, 300 + (step*i), buttons);
})(i);
}
}

Related

Curious to know step by step Process of for loop which involves let and var [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 3 years ago.
I have 2 simple code snippet about for loop involving let and var separately.
First code which has a variable declared with let
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
so it will show o/p like this
0123456789
but if I replace let with var like this
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
it will print 10 for ten times.
I know it is something related to function level scope and block-level scope, but want to clearly understand step by step process of execution.
Thanks in advance.
The reason why you are only printing 9 is that the callback function is executed after the loop is done. Which means that i is 9.
You can:
If you are trying to print 1 - 10 after 1 sec, you can loop in the callback function. Like:
setTimeout(function() {
for (var i = 0; i < 10; i++) { //Put the loop inside the setTimeout db function.
console.log(i);
}
}, 1000);
If you are trying to print every one sec, you can pass the i as the 3rd parameter on setTimeout
for (var i = 0; i < 10; i++) {
setTimeout(function(o) { //Receive it on variale o
console.log(o);
}, 1000 * i, i); //Pass the i as third parameter
}
Doc: setTimeout

JavaScript Why does the index of a for loop add one when adding eventlisteners [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
I have a question which might sound silly. In the code below there are 2 console.log(i) statements. I want to know why does the second console.log(i) statement returns value of 2 and not 1 as the former on the first iteration (i.e. 1st statement i=n, 2nd: i=n+1). Shouldn't both be equal to 1 until the end of the loop?
function toggleWrapper(){
var el1 = document.querySelectorAll('[class="tCell entryDesc"]');
for (var i = 1; i < el1.length; i++) {
console.log(i);
el1[i].addEventListener('click', function(ev){
console.log(i);
var el2=document.querySelectorAll('[class="additionalInfoContainer"]');
if (el2[i-2].clientHeight) {
el2[i-2].style.maxHeight = 0;
}
else{
el2[i-2].style.maxHeight = el2[i-2].scrollHeight +"px";
}
},
false);
}
}
The problem is that the variable i, within each of your addEventListener() functions, is bound to the same variable outside of the function. simply change your for loop to :
for (let i = 1; i < el1.length; i++)
In the loop with let based index, each iteration through the loop will have a new value of i where each value is scoped inside the loop, so your code would work fine.
i think is something in your code because if you try to make a for loop with two "console.log()" it doesn't do that

Why does this javascript for loop cause an early exit? [duplicate]

This question already has an answer here:
(infinite?) loop in javascript code
(1 answer)
Closed 5 years ago.
Right now I am really stumped. I have a short function, called "validate", and for some reason the for loop I have prevents an outer for loop from running.
Here it is breaking by only printing out the first entry:
function validate(str) {
for(i=0; i<str.length; i++) {
// do nothing
}
return str;
}
And here's the version that works:
function validate(str) {
/*for(i=0; i<str.length; i++) {
// do nothing
}*/
return str;
}
Here is my fiddle.
Here is the sample text file.
Try encapsulating your variable i. var i = 0;
function validate(str) {
for(var i = 0; i < str.length; i++) {
// do nothing
}
return str;
}
Without the var you are adding it to the global scope or the window object

Strange logic with setInterval()

I'm new to learning javascript and getting strange behaviour that I don't understand
So this is printing exactly what I expect. 0,1,2,3,4
var numberOfPlayers = 5;
for ( i = 0; i < numberOfPlayers; i++ ) {
alert(i);
}
But when I try the following code where I want to run a function every 1 seconds I get, 5,5,5,5,5
var numberOfPlayers = 5;
for ( i = 0; i < numberOfPlayers; i++ ) {
setInterval(function () {
alert(i);
}, 1000);
}
Can anyone explain to me what is actually happening here. I would expect the same numbers in both parts of code.
In the first instance, you alert the value of i as it goes around the loop.
In the second, you alert the value of i after one second. By the time that second has passed, the loop has finished going around five times, so the value of i at the time is the last value of i.
The I is not bound in the inner function inside the loop -- to something like this to bind it in each iteration of the loop
var numberOfPlayers = 5;
for ( i = 0; i < numberOfPlayers; i++ ) {
(function(i) {
setInterval(function () {
alert(i);
}, 1000);
})(i);
}
You should be getting 5 5 5 5 5. The reason is that your for loop very quickly runs and binds the function to run 5 times. It iterates through itself until i is equal to 5. By the time the second is up and the function is scheduled to run, i is equal to 5. It will then repeat every 1 second 5 times.
As #Quentin explained in his answer, it's an issue of timing and because of the closure inside of setInterval(). There's a good explanation here: Javascript infamous Loop issue?.
For a case like this I would probably use the code #Soren suggested. Another option would be to use a separate function:
var numberOfPlayers = 5;
for ( i = 0; i < numberOfPlayers; i++ ) {
intervalAlert(i);
}
function intervalAlert(i) {
setInterval(function () {
alert(i);
}, 1000);
}
See also http://blog.mixu.net/2011/02/03/javascript-node-js-and-for-loops/

Can someone please explain to me how the scope works for this array of anonymous functions?

Quite simply, I'd like to know why the call to arr0 seems to drag in the value of i instead of the one stored in the function at that position.
<script>
var arr = [];
for(var i = 0; i < 3; i++) {
//Assign anonymous functions to the array in positions 0 to 2
arr[i] = function() { console.log("function " + i); }
}
for(var i = 0; i < 3; i++) {
//The output for these function calls is correct!
arr[i]();
}
//Here I expected to output: function 0, but instead outputs: function 3 WTF!
arr[0] ();
</script>
Here's the output:
function 0
function 1
function 2
function 3
For the last call, i.e: arr[ 0 ] (); I expected the output to be "function 0", but surprisingly IT'S NOT... Could someone please care to explain why?
Thanks in advance!
Well this is a mixed bunch...
You are using the same i variable (despite "re-defining" it in the second loop, it's still the same i) that is placed in the global scope
As a result, in the second loop, each iteration alters the value of that global i, which results in the
function 0
function 1
function 2
function 3
output.
It was, by the way, absolutely not the expected result, if you used k in the second loop:
<script>
var arr = [];
for(var i = 0; i < 3; i++) {
//Assign anonymous functions to the array in positions 0 to 2
arr[i] = function() { console.log("function " + i); }
}
for(var k = 0; k < 3; k++) {
//The output for these function calls is correct!
arr[k]();
}
//Here I expected to output: function 0, but instead outputs: function 3 WTF!
arr[0] ();
</script>
That would produce:
function 3
function 3
function 3
function 3
and that is the infamous loop problem referred in the link above (in comments).
The reason is that functions, defined in your first loop (which, BTW, you should try to avoid in general case), "close" on the variables that are in the same scope as their definition - i in this case. That means that whenever value of i is changed later on, it will be reflected in that function.
The last example shows it in action - i is changed by the first for loop, so when the loop is finished - it's value is 3. All functions you defined now have the same value of i - 3.
To make the output like this:
function 0
function 1
function 2
function 0
you can do this (not that it's that great, structure wise):
var arr = [];
for(var i = 0; i < 3; i++) {
//Assign anonymous functions to the array in positions 0 to 2
arr[i] = (function(index){ return function() { console.log("function " + index); };}(i));
}
for(var k = 0; k < 3; k++) {
//The output for these function calls is correct!
arr[k]();
}
//Here I expected to output: function 0, but instead outputs: function 3 WTF!
arr[0] ();
That produces the ddesire result.
Here, you define an anonymous function:
(function(index){ ... }(i))
that is immediately invoked with i as a parameter. That parameter is referred to as index in the function body (not that it's important, even if you still called it i - it would work, since the "inner" i would shadow the "outer" one).
That function returns a function that has a different closure - on index, which is not important since index is not avialable after the immediately invoked function exists.
Another way would be using some sort of iterator - map, where supported, would do OK.
It's a common problem, after you've read the duplicate posted in the comments, here's a possible solution:
var arr = [0,1,2].map(function(i){
return function(){
console.log("function " + i);
};
});
for(var i = 0; i < 3; i++) {
arr[i]();
}
arr[0]();
By creating an isolate scope with map we avoid the problem altogether. Using underscore's _.range you could replace your loop patterns with:
_.range(0,10).map(function(index){
...
})
The question linked to in one of the comments will give you the general answer.
But I'll also specifically address what you're seeing here, because it might still be slightly confusing even after understanding the answers to the other question.
The main thing to realize here is that there is only one i variable. Even though it looks like you're declaring it twice with the var keyword, the second "declaration" is essentially ignored and treated like any ordinary assignment. You see, the for keyword does not introduce a new scope in JavaScript. So these two snippets are equivalent:
for (var i = 0; i < 3; i++) {}
And:
var i;
for (i = 0; i < 3; i++) {}
Once you realize that, and you get that the functions you create in the first loop all close over the same i, then you can understand why the second loop appears to be "correct" by your intuition: at the start of the loop you set i to 0, and then after each call you increment it. So even though all of them close over the same i, you're changing its value between calls!
And of course, for that last call, the value of i is still 3 since that's what it was at the end of the second loop and you didn't change it from that.

Categories