Scope of anonymous function as argument
I am working on a very simple application. When the user hovers over any list item ( li
), the text color changes to green, and when there is no mouse, it returns to black.
Why can't we use lis[i]
in the following code snippet inside an anonymous function instead of a keyword this
?
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";
});
};
source to share
When the callback function is executed, the variable i
will have a value lis.length
due to the loop, causing the value to lis[i]
be undefined
.
You can use a function instead forEach
.
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>
source to share
When the function is called, the loop will be completed. There is only one variable i
, and the function always sees its current value. Therefore, if you use i
inside a function, you will see it with a value lis.length
.
There are ways to get around this. If you can use ES2015 (possibly via a transpiler) 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 one i
for each loop.
Or, in older code, you can output the loop body to another function and pass it i
as a parameter. This has the same effect as binding a 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 generated by babel from the ES2015 example I gave above)
source to share
you can use "e.srcElement" to get the 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>
source to share