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.
Related
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.
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);
}
}
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);
}
}
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.
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.