let keyword in the for loop - javascript

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.

Related

javascript /jquery for loop doesn't work with extremely complicated expression

I have this javascript:
<script>
$("#P1").on("blur", function(){$("#Y1").val(getClass(this));});
$("#P2").on("blur", function(){$("#Y2").val(getClass(this));});
$("#P3").on("blur", function(){$("#Y3").val(getClass(this));});
$("#P4").on("blur", function(){$("#Y4").val(getClass(this));});
<script>
It's written in jquery. I want to tokenize it in a for loop. I have tried several iterations of syntax, but I can't get one that works with the complex expression.
<script>
var i;
for (i = 0; i < 5; i++) {
$("#P" +i).on("blur", function(){$("#Y" +i).val(getClass(this));});
}
</script>
How come this doesn't work?
At the end of the for loop, i has a value of 5, so every time the blur handler is called, the interpreter will see i with a value of 5 - and #Y5 doesn't exist if you only have #Y1-4.
Use let instead, which has block scope rather than function scope, so that every iteration has a separate binding for i:
for (let i = 1; i < 5; i++) {
$("#P" + i).on("blur", function(){
$("#Y" + i).val(getClass(this));
});
}
Always use let (or const if you can) when you're using for loops, especially if there's anything asynchronous involved. (Actually, you should be using let or const everywhere you can regardless, but it's especially important with for to avoid bugs)

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.

Using javascript closures for context in loops

Is it bad practice to make a jQuery closure simply for the purpose of retaining context?
Example:
{
testFunction: function() {
//Instantiate variables etc...
for (var i = 0; i < columns.length; i++) {
for (var j = 0; j < columns[i].length; j++) {
// create a closure so the .then callback has the correct value for x
var func = function() {
var x = new testObject(columns[i][j].Id);
// find method returns a jQuery promise
x.find().then(function() {
// use x for some logic here
});
};
func();
}
}
}
}
In this case the closure is needed so that the function for the jQuery promise has context for what x is. Otherwise the x variable is changed after the next iteration of the loop. The jQuery promise object gets the value of whatever x was in the last iteration of the loop.
I'm looking for best practices here, just wondering what to do to keep code simple, readable and efficient.
Please note any performance issues that come along with closures/not using closures. References are appreciated.
Using a closure is definitely a good practice, but you can simplify it a little:
Pass in the object directly to your closure.
Use an IIFE instead of a named function declaration.
for (var i = 0; i < columns.length; i++) {
for (var j = 0; j < columns[i].length; j++) {
(function(x) {
x.find().then(function() {
// use x for some logic here
});
}( new testObject(columns[i][j].Id) ));
}
}
Note: you could simplify this code a little by using jQuery's $.each() method, but using a regular for loop is way faster.
jQuery (or ES5) iterators usually lead to cleaner code compared to loops. Consider:
$.each(columns, function() {
$.each(this, function() {
var x = new testObject(this.Id);
x.find().then(function() {
// stuff
});
});
});
Note that the scoping problem doesn't arise here, since you get a new scope on every iteration automatically. To address the performance question, iterators are slower than loops, but in the event-driven world you hardly need to worry about that.
Unfortunately there is no a better way to solve the problem. However, you miss to pass i and j:
for (var i = 0; i < columns.length; i++) {
for (var j = 0; j < columns[i].length; j++) {
// create a closure so the .then callback has the correct value for x
var func = function(i, j) {
var x = new testObject(columns[i][j].Id);
// find method returns a jQuery promise
x.find().then(function() {
// use x for some logic here
});
};
func(i, j);
}
}
It is ok and it's actually the only way to create a context.
Unfortunately for some reasons I don't really understand there are Javascript engines that limit the number of nested levels of functions you can create to very low numbers so just try to use them only when really needed (e.g. I found that my quite powerful Galaxy S4 with Android only can handle 18 nested levels).
For hand-written code this is not an issue normally, but for generated javascript code it's quite easy to get past those untold limits.

Reusing the same shortcut.js function to handle keyboard input

I'm using shortcut.js to handle keyboard input and I'm wondering if there is a more efficient way to achieve my goal (currently most of the same code is copied and pasted).
For example, i have:
shortcut.add("0",function() {
points = -1;
sec = 0;
});
shortcut.add("1",function() {
points = 1;
sec = 0;
});
shortcut.add("2",function() {
points = 2;
sec = 0;
});
shortcut.add("3",function() {
points = 3;
sec = 0;
});
Ideally, I can generalize the function so that whatever key is entered is actually assigned to the points variable, except in the case where the user enters 0. In that case, the points variable is set to -1.
Any ideas on how to make this happen? Thank you!
A loop with a closure should do the trick:
for (var i = 0; i <= 9; ++i) {
(function(i) { // Capture current value of 'i' in this scope.
shortcut.add(i.toString(), function() {
points = i || -1; // 'i' if 'i' is not 0, else -1.
sec = 0;
});
})(i);
}
Update following comment: So why do we need a closure here? And what does the final (i); mean?
Basically, we need a closure because the anonymous functions passed to shortcut.add() will not be called right away, but some time in the future, after the loop has terminated. The functions capture i by reference, not by value, which means they will see the value of i that is current at the time they run, not at the time they're defined.
So, if we call shortcut.add() directly from the loop body, all the anonymous functions we pass will end up seeing the value of i that is current after the loop has terminated, which will always be the same (10).
Creating a new variable in each iteration looks like it could work, but doesn't:
for (var i = 0; i <= 9; ++i) {
var _i = i; // Create new variable containing current value of 'i'.
shortcut.add(i.toString(), function() {
points = _i || -1; // Won't work, '_i' is always 9.
sec = 0;
});
}
Since for loop bodies do not have their own scope in Javascript, _i ends up in function scope, the same as i, and will be captured the same way (its final value will be 9 instead of 10 because ++i does not apply to it).
So, what we really need here is a new scope in each iteration. To achieve this, we can define a function inside the loop, and call it immediately, passing it the current value of i:
var newScope = function(i) {
// Here, the value of 'i' will be the one current when 'newScope' is called
// and will not change, even if 'i' is captured by other functions.
};
newScope(i); // Call function with current value of 'i'.
Finally, we can do that without introducing the newScope name, by directly applying the call operator () to the function definition:
(function(i) {
// Here, the value of 'i' will be the one current when this function is
// called and will not change, even if 'i' is captured by other functions.
})(i); // Call function with current value of 'i'.
I hope this appropriately answers your questions, feel free to leave further comments if it does not. For more information about closures, see Closures on MDN.

Variable definitions in nested for loops?

Some static languages like Java seem to have very special rules for variables defined in the first argument of a for loop. They are accessible only by the given loop, which makes them behave pretty much like javascript functions' local variables and arguments. I mean stuff like this:
class ForVariable {
public static void main(String[] args) {
for(int i = 0; i != 0; i++) {}
System.out.println(i); // Throws an Exception
}
}
Javascript doesn't behave like that, which makes nesting loops quite a messy business. My question is: Is it valid to declare variables in the subsequent loops via the var keyword? In other words - which of the following examples is valid?
for(var i = 0, j; i < 5; i++) {
for(j = 0; j < 10; j++) <do some stuff>;
}
OR
for(var i = 0; i < 5; i++) {
for(var j = 0; j < 10; j++) <do some stuff>;
}
Clearly it is wrong to declare a variable several times, which would make the 2nd example a no-go, but given the fact that the 1st example is the way loops nesting is done in most languages I know, I'm rather hesitant to declare the winner.
Those are both valid. Function scoped vs block scoped. Basically both loops in JavaScript become:
function a () {
var i, j;
for(i = 0, j; i < 5; i++) {
for(j = 0; j < 10; j++) <do some stuff>;
}
}
because the var declarations are hoisted to the top
Its not wrong to declare a variable several times. For instance there is really no problem with:
var i = 0;
var i = 1;
That's valid JavaScript. Good tools like the Closure Compiler will generate a warning though because you typically don't intend to do that.
That being said, even the Closure Compiler won't generate a warning for your example #2. It's just common convention in JS even if you are technically re-declaring.
Either of your two examples is fine but the second one is a little more sensible to parse. I wouldn't worry about it either way.
You don't want to be using the var keyword, but rather function arguments, because javascript is not block-scoped. For example:
[100,200,300].forEach(function (x,i) {
[10,20,30].forEach(function (y,j) {
console.log('loop variables, indices '+[i,j]+' have values: '+[x,y]);
});
})
or
[100,200,300].map(function (x,i) {
return [10,20,30].map(function (y,j) {
return x+y;
});
})
// result: [[110,120,130],[210,220,230],[310,320,330]]

Categories