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

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.

Related

Closure inside a for Loop in Javascript with confusing results [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I'm a beginner in JavaScript, I've read every answer in similar topics, I still can't understand what's really going on because no one explained the part that I'm confused about.
I have a HTML document with two paragraphs, and this is what I'm using to change the color of a paragraph to red when I click on it :
var func = function() {
/*Line 1*/ var paragraphs = document.querySelectorAll('p')
/*Line 2*/
/*Line 3*/ for (var i = 0; i < paragraphs.length; i++) {
/*Line 4*/ p = paragraphs[i]
/*Line 5*/ p.addEventListener('click', function() {
/*Line 6*/ p.classList.toggle('red')
/*Line 7*/ })
/*Line 8*/ }
}
func();
The result is, where ever I click, only the last paragraph changes color to red. All the answers to questions similar to mine said that after the For loop is finished, the value of i will be 1, so that's what the closure will use, and then the eventListener will be added to same second paragraph ? And that I can fix this using a Immediately-Invoked Function or let to make i private to the closure, I don't know why should I make ì private if the closure has access to it at each iteration of the loop..
I just can't understand what's really going on here, isn't the loop executed line after another? At the start i will have a value of 0, so at Line 4 the variable p will have the first paragraph, then at Line 5-6 the function will use that p and attach the listener to it, then the loop gets executed a second time and i will have a value of 1, then at Line 5-6 again the closure gets the new value of p ?
I know the closure have access to the global variables here like i, so it have access to the p at Line 4 when its value changes.
What am I missing here please? Thank you very much in advance!
You are showing the proverbial closure example....
This can be difficult to grasp at first
/*Line 1*/ var paragraphs = document.querySelectorAll('p')
/*Line 2*/
/*Line 3*/ for (var i = 0; i < paragraphs.length; i++) {
/*Line 4*/ p = paragraphs[i]
/*Line 5*/ p.addEventListener('click', function() {
/*Line 6*/ p.classList.toggle('red')
/*Line 7*/ })
/*Line 8*/ }
Lines 5, 6 and 7 contain the anonymous callback function that is being stored in each paragraph. That function relies on the variable i from the parent function because the inner function uses p which is defined as paragraphs[i]. So, even though you aren't using i explicitly in the inner function, your p variable is. Let's say there are 7 paragraphs in the document... Because of this, you have 7 functions that are "closed" around the one i variable. i cannot go out of scope when the parent function terminates because the 7 functions need it. So, by the time a human comes along to click on one of the paragraphs, the loop has completed (i will now be 8) and each function is looking at the same i value.
To solve the problem, the click callback functions need to each get their own value, rather than share one. This can be accomplished in several ways, but they all involve PASSING a copy of i into the click callback function so that a COPY of the i value will be store in each of the click callback functions or removing the use of i alltogether. There will still be a closure, but there won't be the side effects you initially encountered because the nested functions won't be relying on variables from a parent function.
Here's an example that removes i from the nested function, thus solving the problem:
var paragraphs = document.querySelectorAll('p')
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].addEventListener('click', function() {
this.classList.toggle('red')
});
}
.red {color:red;}
<p>Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
<p class="red">Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
A closure is a function that closes in the values of the variables, so they don't change on the next iteration of the loop, which happens before the click happens etc.
var paragraphs = document.querySelectorAll('p');
for (var i = 0; i < paragraphs.length; i++) {
(function(p) { // <- any function call, would create a new scope, and hence a closure
p.addEventListener('click', function() {
p.classList.toggle('red'); // note that "this" would work here, instead of "p"
});
})(paragraphs[i]); // <- in this case, it's an IIFE, but it doesn't have to be
}
That would be a closure
In JavaScript (ECMA-Script 5 and earlier versions), only functions can create scopes.
In the other hand, closures don't capture variable values. That is, you need to do it yourself as you've already said using IIFEs (immediately-invoked function expressions):
for (var i = 0; i < paragraphs.length; i++) {
(function(p) {
p.addEventListener('click', function() {
p.classList.toggle('red')
})
})(paragraphs[i]);
}
BTW who knows if you can simplify this code using document.querySelectorAll:
// Now you don't need IIFEs anymore...
// Change the "p" selector with whatever CSS selector
// that might fit better in your scenario...
Array.from(document.querySelectorAll("p"))
.forEach(function(p) {
p.addEventListener("click", function() {
p.classList.toggle('red');
});
});
Well, in fact, you can refactor your code to use Array.prototype.forEach and you won't need to use IIFEs too:
paragraphs.forEach(function(p) {
p.addEventListener("click", function() {
p.classList.toggle('red');
});
});
This is the classic problem caused by your variable p being bound to a variable whose value has changed by the time the callback is invoked.
For iterating over an array and calling async code without any special tricks you can simply use Array.prototype.forEach (and on some browsers the classList property also supports this method):
paragraphs.forEach(function(p) {
p.addEventListener('click', function() {
p.classList.toggle('red');
});
});
Since p is a bound parameter of the forEach callback it always holds the expected value for the current iteration, and the correct element with toggle.
If your browser doesn't support classList.forEach then use:
[].prototype.forEach.call(paragraphs, ...);

Closures inside loops and local variables [duplicate]

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;

Javascript: Creating Functions in a For Loop

Recently, I found myself needing to create an array of functions. The functions use values from an XML document, and I am running through the appropriate nodes with a for loop. However, upon doing this, I found that only the last node of the XML sheet (corresponding to the last run of the for loop) was ever used by all of the functions in the array.
The following is an example that showcases this:
var numArr = [];
var funArr = [];
for(var i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
window.alert("Num: " + numArr[5] + "\nFun: " + funArr[5]());
The output is Num: 5 and Fun: 10.
Upon research, I found a a segment of code that works, but I am struggling to understand precisely why it works. I reproduced it here using my example:
var funArr2 = [];
for(var i = 0; i < 10; ++i)
funArr2[funArr2.length] = (function(i){ return function(){ return i;}})(i);
window.alert("Fun 2: " + funArr2[5]());
I know it has to do with scoping, but at first glance it does not seem like it would perform any differently from my naive approach. I am somewhat of a beginner in Javascript, so if I may ask, why is it that using this function-returning-a-function technique bypasses the scoping issue? Also, why is the (i) included on the end?
Thank you very much in advance.
The second method is a little clearer if you use a parameter name that does not mask the loop variable name:
funArr[funArr.length] = (function(val) { return function(){ return val; }})(i);
The problem with your current code is that each function is a closure and they all reference the same variable i. When each function is run, it returns the value of i at the time the function is run (which will be one more than the limit value for the loop).
A clearer way would be to write a separate function that returns the closure that you want:
var numArr = [];
var funArr = [];
for(var i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = getFun(i);
}
function getFun(val) {
return function() { return val; };
}
Note that this is doing basically the same thing as the first line of code in my answer: calling a function that returns a function and passing the value of i as a parameter. It's main advantage is clarity.
EDIT: Now that EcmaScript 6 is supported almost everywhere (sorry, IE users), you can get by with a simpler approach—use the let keyword instead of var for the loop variable:
var numArr = [];
var funArr = [];
for(let i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
With that little change, each funArr element is a closure bound do a different i object on each loop iteration. For more info on let, see this Mozilla Hacks post from 2015. (If you're targeting environments that don't support let, stick with what I wrote earlier, or run this last through a transpiler before using.
Let's investigate what the code does a little closer and assign imaginary function names:
(function outer(i) {
return function inner() {
return i;
}
})(i);
Here, outer receives an argument i. JavaScript employs function scoping, meaning that each variable exists only within the function it is defined in. i here is defined in outer, and therefore exists in outer (and any scopes enclosed within).
inner contains a reference to the variable i. (Note that it does not redefine i as a parameter or with the var keyword!) JavaScript's scoping rules state that such a reference should be tied to the first enclosing scope, which here is the scope of outer. Therefore, i within inner refers to the same i that was within outer.
Finally, after defining the function outer, we immediately call it, passing it the value i (which is a separate variable, defined in the outermost scope). The value i is enclosed within outer, and its value cannot now be changed by any code within the outermost scope. Thus, when the outermost i is incremented in the for loop, the i within outer keeps the same value.
Remembering that we've actually created a number of anonymous functions, each with its own scope and argument values, it is hopefully clear how it is that each of these anonymous functions retains its own value for i.
Finally, for completeness, let's examine what happened with the original code:
for(var i = 0; i < 10; ++i){
numArr[numArr.length] = i;
funArr[funArr.length] = function(){ return i; };
}
Here, we can see the anonymous function contains a reference to the outermost i. As that value changes, it will be reflected within the anonymous function, which does not retain its own copy of the value in any form. Thus, since i == 10 in the outermost scope at the time that we go and call all of these functions we've created, each function will return the value 10.
I recommend picking up a book like JavaScript: The Definitive Guide to gain a deeper understanding of JavaScript in general so you can avoid common pitfalls like this.
This answer also provides a decent explanation on closures specifically:
How do JavaScript closures work?
When you invoke
function() { return i; }
the function is actually doing a variable look-up on the parent call-object (scope), which is where i is defined. In this case, i is defined as 10, and so every single one of those functions will return 10. The reason this works
(function(i){ return function(){ return i;}})(i);
is that by invoking an anonymous function immediately, a new call-object is created in which the current i is defined. So when you invoke the nested function, that function refers to the call-object of the anonymous function (which defines whatever value you passed to it when it was invoked), not the scope in which i was originally defined (which is still 10).

The purpose of "Self Invoking Anonymous Functions" [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What is the purpose of a self executing function in javascript?
Hopefully quite a straight forward question:
What is the purpose of using self invoking anonymous functions? Is it simply to prevent "polluting" the global scope with variables etc.? Or are there other advantages to using them?
Out of my personal experience, other than using anonymous functions for inducing a scope, I have also used it in for-loops for closure. This can be useful when a DOM element needs to store its count and you don't have access to libraries like jQuery etc.
Let's say you have a 100 DIV elements. Clicking the first DIV element should alert 1, similarly clicking the 56th div element should alert 56.
So when creating these elements, you normally do something like this
// Assume myElements is a collection of the aforementioned div elements
for (var i = 0; i < 100; ++i) {
myElements[i].onclick = function() {
alert( 'You clicked on: ' + i );
};
}
This will alert 99, as the counter is currently 99. The value of i is not maintained here.
However, when an anonymous function is used to tackle the problem,
for (var i = 0; i < 100; ++i) {
(function(count){
myElements[count].onclick = function() {
alert( 'You clicked on: ' + count );
};
})(i);
}
Here the value of i is maintained and the correct count is displayed.
Is it simply to prevent "polluting" the global scope with variables etc.?
Pretty much. Encapsulation and avoiding as much global state as possible are good goals in themselves.
It is to create its own scope. It is not only better because you no longer "pollute" some other (global, for example) scope, it gives you guaranteed escape for name collision concerns and defense from programmers that like to poke inside internals of your functions/objects/methods too much among all the benefits. It also allows GC to easily understand that you don't need any of referenced objects anymore when function is done.
Closures in for-loops also use self invoking anonymous functions.
function attachEventsToListItems( ) {
var oList = document.getElementById('myList');
var aListItems = oList.getElementsByTagName('li');
for(var i = 0; i < aListItems.length; i++) {
var oListItem = aListItems[i];
// Watch this:
oListItem.onclick = (function(value) {
return function() {
alert(value);
}
})(i);
}
}

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