Why is 8 showing up and not the number referring to i?

Asked

Viewed 109 times

4

<style>

.menu > li:hover .sub-menu{
    display:block;
}

</style>

<ul class="menu">
    <li>li 1</li>
    <li>li 2</li>
    <li>li 3
        <ul class="sub-menu">
            <li>li 1</li>
            <li>li 2</li>
        </ul>
    </li>
    <li>li 4
        <ul class="sub-menu">
            <li>li 1</li>
            <li>li 2</li>
        </ul>
    </li>
</ul>

<script>

    var x = document.getElementsByTagName("ul");
    var y = document.getElementsByTagName("li");
    var i;


    for(i=0;i<x.length;i++){
        if(x[i].className == "sub-menu"){
            x[i].style.display="none";
        }
    }

    for(i=0;i<y.length;i++){

        if(y[i].childElementCount > 0){

            y[i].onclick=function(){
                console.log(x[i]);
            }

        }
    }



</script>
  • 1

    I think it’s a duplicate of this http://answall.com/q/1237/129, the problem is the same.

2 answers

5


Notice that the i is a variable that changes in this scope. When the console.log(x[i]); was the run i already received another value because it was within the loop.

In ES6 (the new version of Javascript) you can use the let and your problem goes away. The let creates a variable in the scope of this code block and therefore does not interfere with the global variable. Take a look here:

for (let i = 0; i < 10; i++){
  setTimeout(function(){ console.log(i); }, 500); 
  // dá sempre o numero certo, apesar de ser meio segundo depois
}

online example: http://www.es6fiddle.net/inyva5bh/

But in your example, using the version that old browsers use, you have to pass that i a function to be saved with the value it had at the time. For example:

function handler(index){
    return function(){
        console.log(x[index]);
    }
}

and within the for do so:

for(i=0;i<y.length;i++){
    if(y[i].childElementCount > 0){
        y[i].addEventListener('click', handler(i));
    }
}

jsFiddle: https://jsfiddle.net/skuvhffg/1/

or closer to your code:

for (i = 0; i < y.length; i++) {
    if (y[i].childElementCount > 0) {
        (function(ind) {
            y[ind].onclick = function() {
                console.log(x[ind], ind, i);
            }
        })(i);
    }
}

example: https://jsfiddle.net/skuvhffg/2/

You have an explanation of this problem and alternative solutions in this other question also.

4

As @Sergio mentioned, the problem is similar to this question:

How to use the current value of a variable in a more internal function?

However, perhaps the solutions there are not suitable for your specific solution.

Basically what happens is that when the loop ends, the value of i remains at last after loop, and the called functions will use the i current (and not the i from the moment of the loop).

An alternative would be to store the individual value in the element itself, and for this dataset is a great solution:

for(i=0;i<y.length;i++){
  if(y[i].childElementCount > 0){
    y[i].dataset.myId = i;              // Gravamos o i no próprio elemento, como myId
    y[i].onclick=function(){
      console.log( this.dataset.myId ); // Recuperamos o valor com this.dataset.myId
    }
  }
}

So for every element of your loop, we’re creating an attribute myId within the element itself, and by clicking on the element, we recover its individual value with this.dataset.myId.


Simplified example:

Its original code generates a unique Function for the submenus, I made an individual version by li just to illustrate:

var y = document.getElementsByTagName("li");
var i;

for(i=0;i<y.length;i++){
  y[i].dataset.myId = i;
  y[i].onclick = function(){
    alert( 'Voce clicou no li ' + this.dataset.myId );
  }
}
<ul>
  <li>li 0</li>
  <li>li 1</li>
  <li>li 2</li>
  <li>li 3</li>
  <li>li 4</li>
  <li>li 5</li>
  <li>li 6</li>
</ul>

Browser other questions tagged

You are not signed in. Login or sign up in order to post.