4

I'm trying to add a mouseenter event listener to elements to change their childnodes' opacity. I use for to add the listener, and can output the childnodes properly, but in the function it's "undefined". I have no idea why.

HTML:

<article class="index-image-article rel">
    <section class="index-image-info">
        // some elements
     </section>
     <div class="index-image-excerpt mask table">
         // some elements
     </div>
</article>

Javascript:

var indexImageArticle = document.querySelectorAll(".index-image-article");
var indexImageInfo = document.querySelectorAll(".index-image-info");
var indexImageExcerpt = document.querySelectorAll(".index-image-excerpt");

for(var i = 0; i < indexImageArticle.length; i++) {
    console.log(indexImageInfo[i]); // output properly
    indexImageArticle[i].addEventListener("mouseenter", function() {
        indexImageInfo[i].style.opacity = 1;
        indexImageExcerpt[i].style.opacity = 0;
    });
}

the output of console.log is:

<section class=​"index-image-info">​…​</section>​
<section class=​"index-image-info">​…​</section>​
<section class=​"index-image-info">​…​</section>​
<section class=​"index-image-info">​…​</section>

and the error:

Uncaught TypeError: Cannot read property 'style' of undefined(anonymous function)

​the undefined point to the indexImageInfo[i]

Brick Yang
  • 5,388
  • 8
  • 34
  • 45
  • 3
    Try to imagine, what value `i` has at the moment, when the the event handler function is called. – Boldewyn Jul 22 '15 at 13:52
  • As a follow up to @ctwheels comment, do you have any "index-image-article" elements that do not contain an "index-image-info" element? – Bryce Siedschlaw Jul 22 '15 at 13:53
  • 1
    This is the most infamous `closure inside loop` problem. At the time of execution(!) the loop variable `i` has reached its final state which is one higher than the highest loop value (in your case it's `indexImageArticle.length`). Somehow you'll have to generate a separate scope for a local copy of `i` in each loop. There are several ways to do this (e.g. function factory or IIFE) – devnull69 Jul 22 '15 at 13:55
  • @Jonathan I got "5 5 5" – Brick Yang Jul 22 '15 at 13:57

1 Answers1

13

This is one of those strange nuances of variable scoping rules in javascript.

Really this boils down to one thing, at the point of adding the event handler, i has the right value. At the point of executing the event handler i has been incremented up to indexImageArticle.length, and is therefore outside of the bounds of the array.

It can be fixed by using a block which maintains the scope of the variable, a IIFE is one way:

for(var i = 0; i < indexImageArticle.length; i++) {
    console.log(indexImageInfo[i]); // output properly
    (function(x){
        indexImageArticle[x].addEventListener("mouseenter", function() {
            indexImageInfo[x].style.opacity = 1;
            indexImageExcerpt[x].style.opacity = 0;
        });
    })(i)
}
Jamiec
  • 133,658
  • 13
  • 134
  • 193