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>