Closures inside loops and local variables [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I am a JS novice and I was reading about closures, the common issues that arise due to misunderstanding how closures work and the "setting handlers inside a loop" was a pretty good example. I've also seen and understood the ways to get around this, i.e, by calling another function passing the loop variables as arguments and returning a function. Then I tried to take a dip to see if there are any other ways to get around this and I created the following code.
var i;
var inpArr = new Array();
for(i = 0; i < 10; ++i) {
inpArr.push(document.createElement("input"));
inpArr[i].setAttribute("value", i);
inpArr[i].onclick = function() {
var index = i;
alert("You clicked " + index);
}
document.body.appendChild(inpArr[i]);
}
It doesn't work which I guessed but I don't understand why. I understand i was captured and made available to all the function expressions generated. But why does this still not work after assigning the captured variable to the local variable index? Isn't assigning i the same as passing i as an argument to another function? I mean, isn't i a primitive and isn't it supposed to be copied?
I am confused and I would really appreciate it if someone could tell me what's going on here.

I think you are expecting that var index = i; is being executed in every iteration of the loop, there by setting different values for different index variables, which is not what happens. During each iteration only the function is assigned to the handler, the function is not run.
this sentence gets executed only when you click, by that time the value of i is already the highest value as per your loop. This exact problem is solved by the solutions you read.
what happens during the loop:
inpArr[0].onclick = <a function>; //(local)index=undefined; i=0;
inpArr[1].onclick = <a function>; //(local)index=undefined; i=1;
inpArr[2].onclick = <a function>; //(local)index=undefined; i=2;
inpArr[3].onclick = <a function>; //(local)index=undefined; i=3;
.
.
inpArr[9].onclick = <a function>; //(local)index=undefined; i=9;
and when you click
index = i; //i=9; (local)index=9;

Related

Variable not being stored for than a second in Javascript for loop

Before I begin, I am aware that there are many other articles in StackOverflow about variables not being stored using the Javascript language. However, the solutions to those were mainly simple, such as declaring the variable outside the loop. Unfortunately, this is not working in my case. Here is the code I have:
function authenticationselection() {
var whichformofauthentication =
document.getElementsByName("authenticationtypes")
var len = whichformofauthentication.length
for (i = 0; i < len; i++) {
if (whichformofauthentication[i].checked) {
waytoauthenticate = whichformofauthentication[i].value
console.log(waytoauthenticate)
break
} else {
continue
}
}
}
I have declared the variable waytoauthenticate outside of the foor loop, meaning that it is a global variable. When I run this part of the program, the variable waytoauthenticate is set as the value of one fo the radio buttons (which is what this code is for). However, the variable waytoauthenticate is only defined as the value for one second. After a very, very short period of time, the variable becomes it's original value, which is null. Can anyone help me figure out why the variable is only being defined as the value of the radio button for one second. Thanks!
You are declaring the variable again in the for loop var waytoauthenticate = whichformofauthentication[i].value . Try removing var as it is already declared outside. hope this helps

Why is array component declared outside function not visible inside [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
So I have the following code which doesn't recognise the buttons[i] variable inside the onclick function. Of course if I replace it with 'this', it works fine. (By 'works', I mean it functions as intended and changes the class of the body of the page.) My question is, if the array called 'buttons' is visible inside the anonymous function, why is buttons[i] not visible? Or is it the i that is not visible? I know that 'this' would always be used in such a case but I am just confused as to why buttons[i] does not also work.
(function(){
var buttons = document.getElementsByTagName("button");
for (var i =0, len = buttons.length; i < len; i++)
{
buttons[i].onclick = function(){
debugger
var className = buttons[i].innerHTML.toLowerCase();
// console.log(className);
document.body.className = className;
}
}
}());
Because by the time it's clicked, i == buttons.length, and buttons[i] is therefore undefined. There are many ways to deal with this, including binding the handler to the the right object, or introducing a closure that contains the right reference. One of the most backward-compatible ways is this:
for (var i =0, len = buttons.length; i < len; i++) {
buttons[i].onclick = (function(button) {
return function(){
var className = button.innerHTML.toLowerCase();
document.body.className = className;
};
}(buttons[i]));
}
You're having a problem with closures here. Because the loop will have executed before someone fires the click event your variable i = buttons.length which in this case would be five as there are five buttons. So when you're referencing buttons[i] you are in fact referencing buttons[5] which doesn't exist.
This is to do with javascript closures. This article explains closures quite well
buttons[i].onclick property takes in a function reference as an argument (in your case, you've passed in an anonymous function). The function that you are passing in however, is not in the same scope as your for loop. That means that you have to explicitly pass down any variables that are required inside that function. The variable button is available because you've passed that in to your anon function as a parameter.

javascript string as command line, why not eval()? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Why is using the JavaScript eval function a bad idea?
So I have read through MANY different methods on calling a function from a string, using window[](); and also eval();. I am wondering if for my situation (That is below) what method is exactly the right way to go ahead with, and if so, explain why. Also explain why eval(); isn't exactly a good option, a lot of people say security, but why would security be an issue if you can get any browser plugin that enables you to change the script on that page? (example: firebug for firefox)
My current code:
funcOne(target).funcTwo(x, y, z);
How would call this using the recommended window[](); way? And why can't I use this?:
eval('funcOne(target).funcTwo(x, y, z)');
I don't want your annoyance of this question being asked many times, because I cannot currently think of a way to call a, as a call it, double function.
Thanks in advance!
In Javascript the syntax a.b can be replaced with a["b"]. So in your case you can use
window["funcOne"](target)["funcTwo"](x, y, z);
where of course it makes sense only if you are using variables instead of "funcOne" and "funcTwo".
If everything is instead fixed but you simply want to delay execution you can use "thunking" with an anonymous closure with
x = function(){ return funcOne(target).funcTwo(x, y, z); };
and then you can evaluate with x() to get the desired result later.
The last example will work correctly even if the variables target and x, y and z are local to the enclosing scope because the thunking closure will "capture" them.
You should however pay attention to the fact that in Javascript the only way to create a new scope is to use a function (a block surrounded with { and } is NOT a scope like happens in C++ and other languages).
If you need to create several closures in a loop this can bite back and is a source of a quite common mistake...
for (var i=0; i<options.length; i++)
{
var menu_item = document.createElement("div");
menu_item.textContent = "Option " + i;
menu_item.onclick = function () {
// Warning this will NOT work. All divs will
// alert using the same number!
alert("Option " + i + " selected");
}
menu.appendChild(menu_item);
}
here I used a closure for the onclick event on the div, but this is not going to work because all those functions will use the very same i variable. Because in Javascript the only way to create a scope is using a function the solution is:
for (var i=0; i<options.length; i++)
{
var menu_item = document.createElement("div");
menu_item.textContent = "Option " + i;
(function(i){
menu_item.onclick = function () {
alert("Option " + i + " selected");
};
})(i); // Pass current `i` as parameter
menu.appendChild(menu_item);
}
This way the variable i inside the onclick handler will be different for each closure.
This pattern of creating a function just to call it immediately is often used in Javascript when you need to create many independent closures so it's better to know and understand it.

Strange things in JavaScript "for"

I'm using jQuery and I have a strange thing that I don't understand. I have some code:
for (i = 1; i <= some_number; i++) {
$("#some_button" + i).click(function() {
alert(i);
});
}
"#some_button" as the name says - they are some buttons. When clicked they should pop-up a box with it's number, correct? But they don't. If there is 4 buttons, they always pop-up "5" (buttons count + 1). Why is that so?
It has to do with JavaScript scoping. You can get around it easily by introducing another scope by adding a function and having that function call itself and pass in i:
for (var i = 1; i <= some_number; i++) {
(function(j) {
$("#some_button" + j).click(function() {
alert(j);
});
})(i);
}
This creates a closure - when the inner function has access to a scope that no longer exists when the function is called. See this article on the MDC for more information.
EDIT: RE: Self-calling functions: A self-calling function is a function that calls itself anonymously. You don't instantiate it nor do you assign it to a variable. It takes the following form (note the opening parens):
(function(args) {
// function body that might modify args
})(args_to_pass_in);
Relating this to the question, the body of the anonymous function would be:
$("#some_button" + j).click(function() {
alert(j);
});
Combining these together, we get the answer in the first code block. The anonymous self-calling function is expecting an argument called j. It looks for any element with an id of some_button with the integer value of j at the end (e.g. some_button1, some_button10). Any time one of these elements is clicked, it alerts the value of j. The second-to-last line of the solution passes in the value i, which is the loop counter where the anonymous self-calling function is called. Done another way, it might look like this:
var innerFunction = function(j) {
$("#some_button" + j).click(function() {
alert(j);
});
};
for (var i = 1; i <= some_number; i++) {
innerFunction(i);
}
You are having a very common closure problem in the for loop.
Variables enclosed in a closure share the same single environment, so by the time the click callback is called, the loop will have run its course and the i variable will be left pointing to the last entry.
You can solve this with even more closures, using a function factory:
function makeOnClickCallback(i) {
return function() {
alert(i);
};
}
var i;
for (i = 0; i < some_number; i++) {
$("#some_button" + i).click(makeOnClickCallback(i));
}
This can be quite a tricky topic, if you are not familiar with how closures work. You may to check out the following Mozilla article for a brief introduction:
Working with Closures
Because in the moment you click them, i == 5.
This is because of how closures work in JavaScript. Each of the 5 functions you are creating is basically sharing the same i variable. The value of i inside your function is not being evaluated when you are creating the function, but when the click event occurs, by which time the value of i is 5.
There are various techniques for getting around this (when this behavior isn't what you want). One (if you have a simple function, like you do here) is to use the Function constructor instead of a function literal:
$("#some_button" + i).click(new Function("alert("+i+")");
(function (some_number) {
for (i = 1; i <= some_number; i++) {
$("#some_button" + i).click(function() {
alert(i);
});
}
})(some_number);
Wrap the function outside because for speed and the fact i will keep resetting.
This is very clever code. So clever it's a question on SO. :) I'd sidestep the question altogether by dumbing the code down, just to have a chance at understanding it (or having a colleague understand it) six months from now. Closures have their place, but in this case I'd avoid them in favour of more understandable code.
Probably, I'd attach the same function to all the buttons, which would get the button from the event, strip "some_button" from the ID, and alert the result. Not nearly as pretty, but I guarantee everyone in the office could follow it at a glance.

JavaScript for loop index strangeness [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I'm relatively new to JS so this may be a common problem, but I noticed something strange when dealing with for loops and the onclick function. I was able to replicate the problem with this code:
<html>
<head>
<script type="text/javascript">
window.onload = function () {
var buttons = document.getElementsByTagName('a');
for (var i=0; i<2; i++) {
buttons[i].onclick = function () {
alert(i);
return false;
}
}
}
</script>
</head>
<body>
hi
<br />
bye
</body>
</html>
When clicking the links I would expect to get '0' and '1', but instead I get '2' for both of them. Why is this?
BTW, I managed to solve my particular problem by using the 'this' keyword, but I'm still curious as to what is behind this behavior.
You are having a very common closure problem in the for loop.
Variables enclosed in a closure share the same single environment, so by the time the onclick callback is executed, the loop has run its course and the i variable will be left pointing to the last entry.
You can solve this with even more closures, using a function factory:
function makeOnClickCallback(i) {
return function() {
alert(i);
return false;
};
}
var i;
for (i = 0; i < 2; i++) {
buttons[i].onclick = makeOnClickCallback(i);
}
This can be quite a tricky topic, if you are not familiar with how closures work. You may to check out the following Mozilla article for a brief introduction:
Working with Closures
Note: I would also suggest not to use var inside the for loop, because this may trick you in believing that the i variable has block scope, when on the other hand the i variable is just like the buttons variable, scoped within the function.
You need to store the state of the i variable, because by the time the event fires, the scoped state of i has increased to the maximum loop count.
window.onload = function () {
var buttons = document.getElementsByTagName('a');
for (var i=0; i<2; i++) {
(function (i) {
buttons[i].onclick = function () {
alert(i);
return false;
}
})(i);
}
}
The above example creates an anonymous function with a single argument i, which is then called with i being passed as that argument. This creates a new variable in a separate scope, saving the value as it was at the time of that particular iteration.
It's a order of execution issue
How to assign event callbacks iterating an array in javascript (jQuery)
Basically, the click handler accesses i well after the loop has exited, and therefore i is equal to 2.

Categories

Resources