Variable scope in javascript when iterating through a stack - javascript

I'm baffled. Could someone please explain to me why this produces an infinite loop?
var constant = 4;
var stack = new Array();
stack.push(0);
stack.push(1);
loop1();
function loop1(){
for(i = 0; i < constant; i++){
loop2(i);
}
}
function loop2(num){
for(i = 0; i < stack.length; i++){
console.log(i);
}
}
​
Fiddle: http://jsfiddle.net/elclanrs/tywV9
I suspect it has something to do with Javascript function-level variable scope, but that's as far as my guess goes.

When you don't declare a variable with the var keyword, the variable is global, so loop2 and loop1 are using the same (global) i variable. Every time loop2 is called, i gets set to 0 and then is incremented up to stack.length, which is 2. This means that every iteration of loop1 will end with i=2, which gets incremented to 3 but never reaches constant which is 4.
If you change your for loops to be for (var i = 0; ...) instead of for (i = 0; ...) then this should no longer loop infinitely.

Related

JS Can't use array initialized within for loop

I'm having trouble understanding how scoping works in JS, my background is in R and Python.
This is a toy example. The games_array always prints out as empty at the end. And the array variable doesn't seem to be present in the console.
for(var row_i = 0; row_i < 50; row_i++){
var games_array = [];
if(row_i % 2 == 0){
console.log(data[row_i].name);
games_array.push(data[row_i].name);
}
}
console.log(games_array);
But then this works:
var games_array = [];
for(var row_i = 0; row_i < 50; row_i++){
if(row_i % 2 == 0){
console.log(data[row_i].name);
games_array.push(data[row_i].name);
}
}
console.log(games_array);
I don't understand why I can't create an empty array and use it within a for loop.
I need to wrap this inside an outer loop and use the games_array in the outerloop.
Any help is appreciated.
The problem is not with scoping. After all, since you declared the variable using var instead of let, the scope extends outside of the for loop. The problem is that each time the loop runs, it sets games_array to [], which means the array gets cleared each time the loop runs.
In the second example, you only initialize the array once, which is why it works.

Javascript function with dynamically generated arguments

Below code :
loop(n times)
create HTML Button Element
count++;
assign onclick event = function(){
openSomething("Value_"+count)
}
so if i create 3 input elements (n=3) and then go back click any of the three buttons then every time openSomething("Value_"+3) only gets called.
why openSomething("Value_"+1) and openSomething("Value_"+2) does not get called?
I am not sure what is going on may be it the scope issue but i dont know much about scope either, any help to push me in the right direction is much appreciated.
My original code
var count = 0;
for(var i =0;i<someValue;i++){
count++;
var button = document.createElement("img");
button.src = "/images/small_button.gif";
button.imageButton = true;
button.srcBase = "/images/small_button";
button.onclick = function () {
selectSomething("someIdText_"+count);};
cell.appendChild(button);
}
Because JavaScript doesn't have block-level scoping of variables, and as a result everything is scoped to the function. That means that when you have code that uses a variable (like your loop counter n or your count variable) at a later point (i.e. after the full execution of the function), it will have its value set to the last value for the loop. You need to create a closure (a new scope for the variable) inside of your loop. Something like this (since you didn't post your actual code):
for(var i = 0, l = list.length; i < l; i++) {
(function(count) {
something.onclick = function() {
openSomething("Value_" + count);
}
})(i);
}
For a more modern approtce use let,
works for firefox, chrome, and node
if you need to target all the browsers, use Anthony approach
for(var count = 0, l = list.length; count < l; count++) {
let count;
something.onclick = function() {
openSomething("Value_" + count);
}
}

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.

let keyword in the for loop

ECMAScript 6's let is supposed to provide block scope without hoisting headaches. Can some explain why in the code below i in the function resolves to the last value from the loop (just like with var) instead of the value from the current iteration?
"use strict";
var things = {};
for (let i = 0; i < 3; i++) {
things["fun" + i] = function() {
console.log(i);
};
}
things["fun0"](); // prints 3
things["fun1"](); // prints 3
things["fun2"](); // prints 3
According to MDN using let in the for loop like that should bind the variable in the scope of the loop's body. Things work as I'd expect them when I use a temporary variable inside the block. Why is that necessary?
"use strict";
var things = {};
for (let i = 0; i < 3; i++) {
let index = i;
things["fun" + i] = function() {
console.log(index);
};
}
things["fun0"](); // prints 0
things["fun1"](); // prints 1
things["fun2"](); // prints 2
I tested the script with Traceur and node --harmony.
squint's answer is no longer up-to-date. In ECMA 6 specification, the specified behaviour is that in
for(let i;;){}
i gets a new binding for every iteration of the loop.
This means that every closure captures a different i instance. So the result of 012 is the correct result as of now. When you run this in Chrome v47+, you get the correct result. When you run it in IE11 and Edge, currently the incorrect result (333) seems to be produced.
More information regarding this bug/feature can be found in the links in this page;
Since when the let expression is used, every iteration creates a new lexical scope chained up to the previous scope. This has performance implications for using the let expression, which is reported here.
I passed this code through Babel so we can understand the behaviour in terms of familiar ES5:
for (let i = 0; i < 3; i++) {
i++;
things["fun" + i] = function() {
console.log(i);
};
i--;
}
Here is the code transpiled to ES5:
var _loop = function _loop(_i) {
_i++;
things["fun" + _i] = function () {
console.log(_i);
};
_i--;
i = _i;
};
for (var i = 0; i < 3; i++) {
_loop(i);
}
We can see that two variables are used.
In the outer scope i is the variable that changes as we iterate.
In the inner scope _i is a unique variable for each iteration. There will eventually be three separate instances of _i.
Each callback function can see its corresponding _i, and could even manipulate it if it wanted to, independently of the _is in other scopes.
(You can confirm that there are three different _is by doing console.log(i++) inside the callback. Changing _i in an earlier callback does not affect the output from later callbacks.)
At the end of each iteration, the value of _i is copied into i. Therefore changing the unique inner variable during the iteration will affect the outer iterated variable.
It is good to see that ES6 has continued the long-standing tradition of WTFJS.
IMHO -- the programmers who first implemented this LET (producing your initial version's results) did it correctly with respect to sanity; they may not have glanced at the spec during that implementation.
It makes more sense that a single variable is being used, but scoped to the for loop. Especially since one should feel free to change that variable depending on conditions within the loop.
But wait -- you can change the loop variable. WTFJS!! However, if you attempt to change it in your inner scope, it won't work now because it is a new variable.
I don't like what I have to do To get what I want (a single variable that is local to the for):
{
let x = 0;
for (; x < length; x++)
{
things["fun" + x] = function() {
console.log(x);
};
}
}
Where as to modify the more intuitive (if imaginary) version to handle a new variable per iteration:
for (let x = 0; x < length; x++)
{
let y = x;
things["fun" + y] = function() {
console.log(y);
};
}
It is crystal clear what my intention with the y variable is.. Or would have been if SANITY ruled the universe.
So your first example now works in FF; it produces the 0, 1, 2. You get to call the issue fixed. I call the issue WTFJS.
ps. My reference to WTFJS is from JoeyTwiddle above; It sounds like a meme I should have known before today, but today was a great time to learn it.

Why is CoffeScript not "reusing" loop variable?

I am new to CoffeeScript and very excited about it. I made some basic loops here. Now, CoffeeScript is defining a loop variable for every loop there is as follows:
var food, _i, _j, _len, _len1;
for (_i = 0, _len = fruits.length; _i < _len; _i++) {
food = fruits[_i];
console.log(food);
}
for (_j = 0, _len1 = vegetables.length; _j < _len1; _j++) {
food = vegetables[_j];
console.log(food);
}
I used to code my loops like this:
for(var i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
for(var i = 0; i < vegetables.length; i++) {
console.log(vegetables[i]);
}
i was my loop variable for every loop (nested loops excluded). Now I learned that you should always declare your variables before defining it. So I changed my coding habits to:
var i;
for(i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
for(i = 0; i < vegetables.length; i++) {
console.log(vegetables[i]);
}
As long as I am in the same scope I did not see anything wrong with it, but the compiled CoffeeScript code left me wondering.
Why would CoffeeScript use a different variable for every loop?
Without having looked at the CoffeeScript source code, here's my (educated) guess:
The CoffeeScript interpreter simply creates a new for loop construct for every for .. in you write. While constructing output JS, it maintains a table of local variable names and appends to that as necessary.
After all, you could nest these for .. in loops, in which case you'd start to need separate loop variables in JS anyway.
It would be necessary to track which variables in a local function scope could be potentially re-used. This is possible, but more complex than it's worth - it simply does not provide any benefit, so CoffeeScript does not do it.
The other side of this is, CS can't count on you only using the variable inside of the loop. While it could be dangerous to rely on your iteration variable for use outside the loop, it's possible you will want to. CS reusing it wouldn't give you that chance, if it always used the same variable.

Categories