why is 8 appearing and not the number referring to i?

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>
    
asked by anonymous 08.05.2016 / 19:07

2 answers

5

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

In ES6 (the new version of JavaScript) you can use let and your problem disappears. The let creates a variable in the scope of this block of code 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
}

example online: link

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

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

and within for do so:

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

jsFiddle: link

or even 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: link

You have an explanation of this problem and workarounds in this other question , too.

    
08.05.2016 / 19:45
4

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

  

How to use the current value of a variable in an inner function?

However, the solutions there may not be suitable for your specific solution.

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

An alternative would be to store the individual value in the element itself, and for this the 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 each element of your loop, we are creating a myId attribute within the element itself, and by clicking on the element, we retrieve its individual value with this.dataset.myId .


Simplified example:

Your original code generates a single function for the submenus, I did an individual version for 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>
    
08.05.2016 / 19:28