3

I'm working on a very simple application. When the user hovers over any list item (li), then the text color changes to green, and when the mouse is out it goes back black.

Why can't we use lis[i] in the following piece of code inside the anonymous function instead of this keyword?

var lis = document.querySelectorAll('li');
var i = 0;
for(; i < lis.length; i++){

    lis[i].addEventListener('mouseover', function(){

    this.style.color = 'green';

    });
    lis[i].addEventListener('mouseout', function(){

    this.style.color ="black";  
    });
};
Erazihel
  • 7,295
  • 6
  • 30
  • 53
Profess Physics
  • 317
  • 4
  • 11

3 Answers3

3

When the callback function will be executed, the i variable will have the value of lis.length because of the loop, leading to the value of lis[i] to be undefined.

What you could do is use the forEach function instead.

var lis = document.querySelectorAll('li');
lis.forEach(function (li) {
  li.addEventListener('mouseover', function (){
    li.style.color = 'green';
  });
  li.addEventListener('mouseout', function (){
    li.style.color ="black";  
  });
});
<ul>
  <li>First LI</li>
  <li>Second LI</li>
</ul>
Erazihel
  • 7,295
  • 6
  • 30
  • 53
  • Actually, ```i``` will still be defined, but stuck on the last value of the for loop (in this case lis.length, making ```lis[i]``` undefined). This is due to Javascript closures. See @Duncan's answer. – Michael May 09 '17 at 08:57
  • 1
    @MichaelYang thanks for pointing it out, I was more focus on the solution than the root of the problem – Erazihel May 09 '17 at 09:01
  • 1
    Of course — your solution is very good! I just wanted to point that out :-) – Michael May 09 '17 at 09:04
2

At the time the function is called the loop will have completed. There is only one i variable and the function always sees its current value. So if you use i from inside the function you will see it with the value lis.length.

There are ways around it. If you can use ES2015 (possibly through a transpiler), then you can write:

const lis = document.querySelectorAll('li');
for(let i = 0; i < lis.length; i++){

    lis[i].addEventListener('mouseover', () => lis[i].style.color = 'green');
    lis[i].addEventListener('mouseout', () => lis[i].style.color ="black");
};

and now you have a different i for each time round the loop.

Or in older code you can push the loop body out to another function and pass i in as a parameter. That has the same effect of binding a different variable for each event:

var lis = document.querySelectorAll('li');

var _loop = function _loop(i) {

    lis[i].addEventListener('mouseover', function () {
        return lis[i].style.color = 'green';
    });
    lis[i].addEventListener('mouseout', function () {
        return lis[i].style.color = "black";
    });
};

for (var i = 0; i < lis.length; i++) {
    _loop(i);
}

(which is the code automatically produced by babel from the ES2015 example I gave above)

Duncan
  • 92,073
  • 11
  • 122
  • 156
-1

you can use "e.srcElement" to get current target like this

let lis = document.querySelectorAll('li');

for (let i = 0; i < lis.length; i++) {
  lis[i].addEventListener('mouseover', function(e){
    e.srcElement.style.color = 'green';
  })
  lis[i].addEventListener('mouseout', function(e){
    e.srcElement.style.color = 'black';
  })
}
<ul>
  <li>First LI</li>
  <li>Second LI</li>
</ul>
GuaHsu
  • 309
  • 3
  • 8