1
for ( var i = 0; i < 8; i++ ) {
    $( "#image" + i ).click( function() {
        console.log( "test: " + i );
    } );
}

I wait for test: 0, test: 1, test: 2... here in the console when I click my image but there are only: "test: 8" messages. Why does it work like this? How to save current value of i-variable? I need it to do something like this:

for ( var i = 0; i < 8; i++ ) {
    $( "#image" + i ).click( function() {
        $( "#anotherImage" + i ).css( "opacity", "0.5" );
    } );
}

I have 8 anotherImages with IDs: anotherImage0, anotherImage1, ..., anotherImage7 :)

JavaRunner
  • 2,455
  • 5
  • 38
  • 52

2 Answers2

3

Functions in JS remember their environment that's why all function in JS are closures

for ( var i = 0; i < 8; i++ ) {
    (function(index){  //now index variable will be correct as they are passed to each function instead of sharing same i
        $( "#image" + index ).click( function() {
            console.log( "test: " + index );
        } );
    })(i);
}

PS : If there are no problems with ES6 usage then please also try the other answer with the suggestion to use let

bugwheels94
  • 30,681
  • 3
  • 39
  • 60
2

It's because the i isn't scoped exclusively to your loop block but scoped either globally or to it's parent function. Once your loop is complete it's value ends up being the grand total of the number of times it was incremented. In order to enforce the block scoping you're aiming to achieve, you could use ES6's let variable assignment:

for (let i = 0; i < 8; i++) {
  $("#image" + i).click(function() {
    console.log("test: " + i);
  });
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<img id="image1" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image2" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image3" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image4" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image5" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image6" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image7" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image8" src="http://placehold.it/350x150" width="350" height="150" />

Alternatively you could create an IIFE in your loop, which will enforce block scoping but imo let is much cleaner:

for (var i = 0; i < 8; i++) {
  (function(index) {
    $("#image" + index).click(function() {
      console.log("test: " + index);
    });
  })(i);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<img id="image1" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image2" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image3" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image4" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image5" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image6" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image7" src="http://placehold.it/350x150" width="350" height="150" />
<img id="image8" src="http://placehold.it/350x150" width="350" height="150" />
Carl Edwards
  • 13,826
  • 11
  • 57
  • 119
  • Nice answer! But **"instead hoisted and has a global scope."** As long as we don't see the complete code we cant be sure about the scope of variable. We can only know the functions share same scope. And how is variable hoisting relevant in this case? – bugwheels94 Dec 05 '16 at 04:26
  • 2
    Ah nice catch! Revised my explanation to reflect this. Good looking out. – Carl Edwards Dec 05 '16 at 04:28