JavaScript - Why does this closure not hold parameters? - javascript

I'm reading an article on how closures work. I'm understanding almost all of it except for Section 5 in that article.
It's talking about how closures work with loops with this example:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
Three alerts come up, each saying item3 undefined. I do understand why it says item 3. The closure from the buildList function holds a reference to item, it doesn't hold the actual value.
What I don't get is why list[i] comes back as undefined in each alert from the result.push... line. Doesn't that closure still have the list parameter from retained?

Doesn't that closure still have the list parameter from retained?
Yes. But the value of i will be equal to list.length and list[list.length] is undefined. If an array has length 3, then the highest accessible index is 2, not 3:
var arr = [1,2,3];
console.log(arr[2]); // 3
console.log(arr[3]); // undefined
Related: JavaScript closure inside loops – simple practical example

buildList and it's inner, anonymous function share context to a certain degree. Changing i, as is done in the for loop, changes it for all the created anonymous functions that are created even after the fact. Change the anonymous function from:
function() {alert(item + ' ' + list[i])}
to something like:
(function(i){return function() {alert(item + ' ' + list[i]);};})(i)
This causes a copy of i's value to be made at the time of creation and assigned into another, different i that overrides the outer i in the context of the returned anonymous function, while still allowing dynamic access to item and list.

Related

Creating Multiple Clickable Divs with p5js

EDIT: I figured it out. I just went a totally different route, making a Button class and using rectangles on my actual canvas to represent them.
I am trying to make multiple clickable buttons programatically in p5js, and I am getting caught up on a problem. All the divs are being created, they display and have the proper HTML associated with them, but no matter which is clicked only the last button's functionality is induced.
Here is a photo:
Those are my buttons on the right, but no matter where you click, the color that is selected is always the purple at the bottom.
Here is my code:
for (var i = 0; i < colorCodes.length ; i++){
var div = createDiv(str(i));
div.parent('control-holder');
div.style('background-color', colorCodes[i]);
div.style('height', '40px');
div.mousePressed(function(){
console.log(div.html()); //always prints 7
changeColor(int(div.html()));
});
buttons.push(div);
}
Any help is greatly appreciated, I don't understand why it isn't working.
This is a common JavaScript trap.
When you use the var keyword, the variable you create has function scope, which means that the variable (in your case, your i variable) keeps its value until the end of the function, not until the end of the loop. Consider this code:
for(var i = 0; i < 10; i++){
console.log('i: ' + i);
}
console.log('final i: ' + i);
This code will print out 0-9 in the loop, and then will print out 10 after the loop exits.
Now, let's change the code a bit:
for(var i = 0; i < 10; i++){
setTimeout(function(){
console.log('i: ' + i);
}, 1000);
}
console.log('final i: ' + i);
Now the loop sets up a timeout callback. This code will print out final i: 10 first, and then will print out i: 10 10 times. This is because the console.log('final i: ' + i); line is happening before the callback functions are called. And since the variable has function scope, it takes the last value that it was given. Here's another example:
var i = 42;
setTimeout(function() {
console.log('i: ' + i);
}, 1000);
i = 37;
You might expect this code to print out 42, but again, the callback function is being called after this code runs, so by the time it's called, i is 37.
Anyway, back to your code. Hopefully the examples above show you what's happening in your code: the var keyword creates a function-scoped variable, which means that by the time your mousePressed callback is called, the variable holds the last value it was set to.
Traditionally, the way to get around this was to go through another function call:
for (var i = 0; i < 10; i++) {
callSetTimeout(i);
}
console.log('final i: ' + i);
function callSetTimeout(i) {
setTimeout(function() {
console.log('i: ' + i);
}, 1000);
}
But as of JavaScript 6, you can use the let keyword instead of var:
for(let i = 0; i < 10; i++){
setTimeout(function(){
console.log('i: ' + i);
}, 1000);
}
This gives your variable block scope which means that the variable is only in-scope inside the for loop. As a result, each iteration of the loop gets its own copy, and your function will now work how you expected.

Closure in JavaScript from CoderSchool

I was doing some studying on CodeSchool. While watching a tutorial I was confused by an example. What they were trying to explain in the video was that closures created in loops won't bind until the last minute. The aim for this code was to take a name check for it in the array and return the name alone with its position(without the zero convention). Since closures don't bind till the last minute this code is returning z 5. I am confused why this code is returning a 5 and not a 4. The length of my array is 4 and the for loop stops before 4 because i < passengerArray.length which is the equivalent of i < 4 therefore the last index checked should be passengerArray[3] which means my (i+1) should be 4 in the end and not 5. I hope that makes sense. This has been bothering me all day.
function assignTorpedo(name, passengerArray)
{var torpedoassignment;
for(var i = 0; i < passengerArray.length; i++){
if(passengerArray[i] == name){
torpedoAssignment = function(){
console.log(name + " " + (i+1));
};
}
}
return torpedoAssignment;
}
var give = assignTorpedo("z",["z","a","b","c"]);
give();
The for loop ends when the test condition fails. Why does it fail? Because i is not less than 4; it's equal to 4. Thus, in the console.log() output you see 5.
Also, the statement
closures created in loops won't bind until the last minute.
is a strange way of describing how things work. As soon as the name is found in the array, the variable is assigned a reference to the constructed function. When assignTorpedo returns that value, the closure exists. In the closure, the value of the variable "i" is already 4.
The following statement increments i by 1 after each pass through the loop. So, after the fourth iteration i will be increased by one and tested against the condition again, and then it will fail.
var arr = ["z","a","b","c"];
for ( var i = 0; i < arr.length; i++ ) {
console.log( i );
}
console.log( i );
The For loop finishes when condition "i < passengerArray.length" being violated which i equals 4 at that time.
then console.log(name + " " + (4 + 1)));
Power Tip:
From one of my lecturers in uni,
"Think a "For" loop like a 'While' loop like this"
i = 0;
while (i < passengerArray.length)
{
...............
i++;
}

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.

weird javascript problem

I'm dynamically inserting some html into the document (by using obj.innerHTML += 'some html'). In that html, there are images with 'imageId_X' ids (ie. imageId_1, imageId_2...). This works fine, but there's something wrong with the following code:
for (var n = 0; n < pConfig.images.length; n++)
{
document.getElementById('imageId_' + n).onclick = function()
{
alert(n);
}
}
There are 4 elements in the pConfig.images and alert(n) always alerts 4. Why is this happening, what am i doing wrong?
The cause of your problem is lamba expression in your code. When you define your anonymous function as onclick handler you bound it for outer variable n, which at the end of the loop is always 4 that the reason you get it always 4.
To do it the way you have planned it to be you need to do the following:
for (var n = 0; n < pConfig.images.length; n++)
{
function( n) {
document.getElementById('imageId_' + n).onclick = function()
{
alert(n);
}
}( n);
}
Hence you define separate scope for variable.
The problem is that each function you create has a reference to the same n variable - the one that is incremented. By the end of the loop, it is at 4 - and all functions you made refer to that variable with that value.
You can work around it with a closure, e.g.:
function closure(n) {
return function() {alert(n);}
}
for (var n = 0; n < pConfig.images.length; n++)
{
document.getElementById('imageId_' + n).onclick = closure(n);
}
Looks like you need a closure. See How do JavaScript closures work?

Categories