The first example below is #62 from John Resign`s Learning Advanced JavaScript http://ejohn.org/apps/learn/#62. It is called Fix the Broken Closures. Example 1 fails 4 times. Example 2, which is only different because it has a wrapper function, passes 4 times. It is example #63 from the same tutorial
Can someone please explain
1) why i == count++ in example 1 fails.
2) why i == count++ passes with the help of the wrapper function. How does the wrapper function change things to make it work?
Thanks in advance.
Example 1
var count = 0;
for ( var i = 0; i < 4; i++ ) {
setTimeout(function(){
assert( i == count++, "Check the value of i." );
}, i * 200);
}
Example 2
var count = 0;
for ( var i = 0; i < 4; i++ ) (function(i){
setTimeout(function(){
assert( i == count++, "Check the value of i." );
}, i * 200);
})(i);
This is pretty straight forward.
Since setTimeout executes "asynchronously", there is no way of telling the exact value of i when the function executes, since the loop carries on running.
By using a function wrapper, effectively you are treating the body of the call as a function and are EXPLICITLY passing in the value of i.
You could clear this up by renaming the function i param to j or something else and update the innards of the function to from i to j
Basically it boils down to scoping
Since i is being incremented in the loop the odds are strong that each time the setTimeout callback is invoked the value of i will be 4.
The wrapper function introduces a new scope allowing the value of the parameter i to maintain its value even though the surrounding i is being incremented by the loop.
function outerScope() {
var x = 2, y = 3;
function innerScope() {
var x = 3;
// Obviously this alerts 3.
alert(x);
// Since we have no 'y' defined, alert the value 3 from the outer scope.
alert(y);
}
// Introduce a new scope.
innerScope();
// Since we have left the inner scope x is now 2.
alert(x);
// Obviously this alerts 3.
alert(y);
}
Related
I’m working on a code to get the index of a clicked element so it can add or remove a class to display or hide the information. For it I used for for iteration. But I don’t understand why is there an (i) after the event handler. I’m kind a newbie to coding so I want to understand everything.
Here’s the JavaScript code:
for (let i = 0; i < questions.length; i++) {
questions[i].addEventListener(‘click’,((e) => {
return function() {
if (clic[e].classList.contains(‘q-answered)) {
clic[e].classList.replace(‘q-answered’, ‘q-answeredno’);
} else if (clic[e].classList.contains(‘q-answeredno’)) {
clic[e].classList.replace(‘q-answeredno’, ‘q-answered’);
}
}
})(i))
}
Let's start by looking at what's happening as though you were using var to iterate through your questions
Put simply, it's making it an immediately-invoked function expression (or IIFE for short) and passing in a parameter you normally wouldn't otherwise have access to.
When a click event handler callback expects a function with a single variable. When the event is handled, the function is invoked and the JS runtime provides a pointer event back to your function to do something with. That's all well and good, but here you want to know the offset of the clicked element in the array from information gleaned out of the scope of this callback.
So you instead change the callback shape. You pass in your own function and wrap it in parentheses. In JS, you pass in the arguments to the function in parentheses following the definition. Your example uses the lambda syntax, so you start with the function itself:
(e) => {return ...;}
But if this is all that were passed in here, you'd get a PointerEvent assigned to e as it matches the anticipated callback shape. So you instead need to wrap this in parentheses:
((e) => {return ...;})
Great, but you want this function to have a very particular value passed in when it executes, so you define the arguments at the end. You're using i as the variable identifying the index of the offset element, so we'll pass that in here.
((e) => { return ...; })(i)
Now, this means that when your event handler function is invoked for the first element, it'll practically look like the following:
((e) => {return ...; })(0); //Zero for the first zero-based index
This precludes the event handler callback assigning its own value to your first variable and means that now the function will be invoked, you'll pass the 0 (or otherwise set index property value) to your e argument and the remainder of the return statement will execute accordingly.
What's this closure thing I've heard of and how might it apply here?
#t.niese brings up a great point in the comments that I originally missed about closures and why they're quite relevant here.
Put simply, in JavaScript, you can refer to variables that are defined within a function's scope, to variables of the calling function's (e.g. parent) scope or any variables on the global scope. A closure is any function that's able to keep these references to these variables regardless of whether the parent has already returned or not.
If I have something like the following:
function listFruits(fruits) {
var suffix = "s";
for (var a = 0; a < fruits.length; a++) {
console.log(`I like ${fruits[a]}${suffix}`);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
As you'd expect, despite assigning suffix outside of the loop, I'm able to use it within the loop to make each fruit name plural. But I can do this using an inner function as well:
function listFruits(fruits) {
var suffix = "s";
function pluralizeName(name) {
return `${name}${suffix}`;
}
for (var a = 0; a < fruits.length; a++) {
var pluralName = pluralizeName(fruits[a]);
console.log(`I like ${pluralName}`);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
Again, despite suffix being assigned in the parent of the pluralizeName function, I'm still able to assign it in the return string to log to the console in my loop.
So, let's put a timeout callback in there and see what happens:
function listFruits(fruits) {
var suffix = "s";
for (var a = 0; a < fruits.length; a++) {
setTimeout(function() {
console.log(`I like ${fruits[a]}${suffix}`);
}, 1000);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like undefineds"
// >> "I like undefineds"
// >> "I like undefineds"
Why didn't we list the fruit we like here as before? a is still being defined in the parent and it does attach the suffix value as it should, so what gives?
Well, our loop starts at a = 0, sets the timeout to execute the callback in 1000ms, then increments a to 1, sets the timeout to execute the callback in 1000ms, then increments a to 2, sets the timeout to execute the callback in 1000ms, then increments a to 3, sets the timeout to execute the callback in 1000ms, then increments a to 4 at which point a is greater than the number of fruits passed in (3) and the loop breaks out. But when the timeout callbacks run then, a now has a value of 4 and fruits[4] is undefined, so we see that in the console logs.
Ok, but we want to be able to reference the value of our iterating index locally so the first callback works against the first object, the second against the second and so on, so how do we make that available to the callback? We use the IIFE approach covered above.
function listFruits(fruits) {
var suffix = "s";
for (var a = 0; a < fruits.length; a++) {
(function() {
var current = a;
setTimeout( function() {
console.log(`I like ${fruits[current]}${suffix}`);
}, 1000);
})();
}
}
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
It works here because we create a new closure for each of the functions created by the loop. When the function is created, we take the current value of a from the parent and assign it to a local variable so that when the setTimeout method fires later, it uses that local current value to properly access the intended index of the fruits array.
But rather than capture the variable as I do above in var current = a;, I can instead pass the a variable into the IIFE as a parameter and it will have exactly the same effect:
function listFruits(fruits) {
var suffix = "s";
for (var a = 0; a < fruits.length; a++) {
(function(current) {
setTimeout( function() {
console.log(`I like ${fruits[current]}${suffix}`);
}, 1000);
})(a);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
Our IIFE populates the current variable with the argument a passed in making it available locally and we get the expected outcome.
But my sample uses let, so how does that change anything?
Prior to ES6, we had only the global and function scopes requiring the use of the IFFEs to introduce local function scopes as we saw above. With ES6, we got a new "block scope" which essentially scopes everything within two curly braces, including any number of child blocks within that, but only if the variable is assigned using the const or let keywords. var still only assigns to a global or function scope.
Let's revisit the example above in which we received all the undefined values and replace our use of var with let.
function listFruits(fruits) {
var suffix = "s";
for (let a = 0; a < fruits.length; a++) { //Note the change to 'let' here
setTimeout(function() {
console.log(`I like ${fruits[a]}${suffix}`);
}, 1000);
}
}
listFruits(['grape', 'apple', 'orange']);
// >> "I like grapes"
// >> "I like apples"
// >> "I like oranges"
And this time it works because the value of a persists as assigned to the setTimeout callback's block scope.
Ok, so what about my sample?
Let's bring it back full-circle then. If you were using var, your sample scopes the present value of i to the IIFE so each of your event handler callbacks can access the appropriate offset node.
But since you're using let, the use of an IFFE certainly won't hurt anything, but it's unnecessary as the intended value of i is available to your callback functions via their block scope. As such, you can simplify to remove the IIFE and suffer no consequences.
for (let i = 0; i < questions.length; i++) {
questions[i].addEventListener(‘click’, function() {
if (clic[i].classList.contains(‘q-answered)) {
clic[i].classList.replace(‘q-answered’, ‘q-answeredno’);
} else if (clic[i].classList.contains(‘q-answeredno’)) {
clic[i].classList.replace(‘q-answeredno’, ‘q-answered’);
}
});
}
Should you have questions about any of this, please leave a comment and I'd be happy to edit to address it.
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/
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.
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.
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.