Reputation: 63
Why does this piece of code give me a result of undefined? I have tested in the web browser. It shows me that li[i] is undefined. And I don't know why and how. I know about closure. And maybe it should pop out the string 'dataToggle' every time I click each li element; But someone could help me with more details about this specific question and what you think is important in understanding this question? Thanks
(function(){
var innerDiv = document.getElementsByClassName('showDiv')[0];
//var li = document.querySelectorAll('ul.idiv li');
var li = document.getElementsByTagName('li');
for(var i=0,len=li.length; i<len; i++){
li[i].addEventListener('click',function(e){
e.preventDefault();
alert(li[i]);
var data = li[i].dataset.info;
showDiv.innerHTML = data;
},false);
}
}());
<div class="showDiv">Show data: </div>
<div class="outerDiv">
<ul class="idiv">
<li data-info="datashow"><a href="">111</a></li>
<li data-info="dataHide"><a href="">222</a></li>
<li data-info="dataToggle"><a href="">333</a></li>
</ul>
</div>
Upvotes: 1
Views: 1330
Reputation: 4216
This is because, i
is a variable defined outside your event handler function, which is incremented by each iteration. So when you finish the iterations of the for
loop, the value of i
is equal tolen
, which causes li[i]
to be undefined
. And if you ask me why it's value is not considered during the iteration, it's because your event handler function is only executed when the event occurs (not when you are setting the event handlers by for
loop). So you can make a variable inside the function scope which won't be changed by iteration. Better use this
from inside the event handler to get the same thing.
for(var i=0,len=li.length; i<len; i++){
li[i].addEventListener('click',function(e){
e.preventDefault();
alert(this);
var data = this.dataset.info;
showDiv.innerHTML = data;
},false);
}
Why the value of i
is equal to len
after for
loop is finished? Let's have a simpler example to make you understand the situation.
var len = 2;
for(var i=0; i<len; i++; {
//Do anything here
}
console.log("after for loop: i = " + i);
Lets's go through the iterations.
i = 0
, matches the condition i<len
, which proceeds with executing the code block and executes i++
after that, which makes i=1
;i = 1
now, matches the condition i<len
, which proceeds with executing the code block and executes i++
after that, which makes i=2
;i = 2
now, fails to match the condition i<len
and stops the iteration. So, you have set the i=2
before you go to step 3. So your final console.log
after for
loop will say, after for loop: i = 2
Upvotes: 3
Reputation: 1760
Another approach in addition to the answers of other posters is to use a local scope with function
. In JavaScript function
is the most reliable way of creating a new scope. Taking the above example,
for (var i = 0; i < li.length; i++) {
(function(i) {
var j = i;
li[j].addEventListener('click', function(e) {
e.preventDefault();
var data = li[j].dataset.info;
showDiv.innerHTML = data;
}, false);
})(i);
}
I have created a new scope that that will isolate the value of variable i
. If you're using ES6 you can use let
keyword to do just the same thing. All you have to do is replace var
with let
. You can also use let
in a for
loop that will give you a whole new scope that is local to for
loop.
Upvotes: 1
Reputation: 50291
You need to refer to the particular li
on which you want to attach the event
(function(){
var innerDiv = document.getElementsByClassName('showDiv')[0];
var li = document.getElementsByTagName('li');
for(var i=0,len=li.length;i<len;i++){
var thisLi = li[i];
thisLi.addEventListener('click',function(e){
e.preventDefault();
alert(thisLi.innerHTML);
var data = thisLi.getAttribute('data-info');
innerDiv.innerHTML = data;
},false);
}
}());
When click
will be executed i
will have value as 3 (in your case) so li[i]
will be different. You can check the console of THIS to check value of i
on click
event
Upvotes: 0
Reputation: 1536
This occurs because of scope. Inside the EventListener
, you have an anonymous function, because of this, you are no longer in the scope of li
. However, you can refer to it with the this
keyword. See the alert that I've replaced in your code.
(function(){
var innerDiv = document.getElementsByClassName('showDiv')[0];
//var li = document.querySelectorAll('ul.idiv li');
var li = document.getElementsByTagName('li');
for(var i=0,len=li.length; i<len; i++){
li[i].addEventListener('click',function(e){ // you are now inside a different function block here.
e.preventDefault();
alert(this.innerHTML);
var data = li[i].dataset.info;
showDiv.innerHTML = data;
},false);
}
}());
<div class="showDiv">Show data: </div>
<div class="outerDiv">
<ul class="idiv">
<li data-info="datashow"><a href="">111</a></li>
<li data-info="dataHide"><a href="">222</a></li>
<li data-info="dataToggle"><a href="">333</a></li>
</ul>
</div>
Upvotes: 1