0
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Ovi Maps API Example</title>
   
  </head>
  <body>
      <button id="butt1" name="butt" type="button">Click Me!</button>
      <button id="butt2" name="butt" type="button">Click Me!</button>
      <button id="butt3" name="butt" type="button">Click Me!</button>
  </body>
   <script src="jquery.js" type="text/javascript" charset="utf-8"></script>
   <script src="foo.js" type="text/javascript" charset="utf-8"></script>
</html>

and...

$(document).ready(function() {
    //alert("ready");
    var myClickFunctions = new Array();
    
    //add event handlers to all three buttons
    for(var i=1;i<=3;i++){
      
      myClickFunctions[i]=function(){
        var index = i;
        alert(index);
      }
      
      var buttonID = "#butt";
      var button  = $(buttonID + i);    
      button.click(myClickFunctions[i]);
    }
    
 });

Every button prints 4. Why is this and what is a good way to make each one print the value of i in which the handler was created?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
nsfyn55
  • 14,875
  • 8
  • 50
  • 77

4 Answers4

4

Read up on JavaScript closures and how they work. The fact is the i in button.click(myClickFunctions[i]); at the bottom will be 4 at the end. Remember the that var index=i isn't set when the function is declared, only when it is called. What you need to do is wrap the function in a closure like so:

  myClickFunctions[i]=(function(i){
    return function(){
      var index = i;
      alert(index);
    }
  })(i)

or better yet, do this:

//put this anywhere
function myClickFunctions(i){
  return function(){
    alert(i);
  }
}

//and at the end:
button.click(myClickFunctions(i));

The myClickFunctions function will take the present value of i, and return a function with that value already set. That is the proper way to do this.

Richard Hoffman
  • 729
  • 3
  • 10
  • Thanks for the speedy response. I am vaguely familiar with closures. but the key I was missing was that the variable index isn't set until the function is called. – nsfyn55 Jul 29 '11 at 17:10
2

Your anonymous function that you are setting to myClickFunctions[i] is creating a closure around i. Closures close on the variable itself, not the value at the time the closure is made. So by the time that function actually runs, i is 4 and therefore you will always get 4.

A simple fix is to invoke a function that returns a function, and captures the current value of i.

myClickFunctions[i] = (function(curI) {
     return function() {
        var index = curI;
        alert(index);
     };
})(i);

If that's confusing, then I recommend reading up on closures in JavaScript. They become second nature after a while.

Matt Greer
  • 60,826
  • 17
  • 123
  • 123
0

Because you're assigning a new value to the same variable every time the function loops, therefore var Index is 4 when any button is clicked.

A quick approach to making each anchor tag print out its index would to attach something like the following:

$('a').click(function() {
    var theAnchors = $('a');
    for (i in theAnchors)
        if (theAnchors[i] == this)
            alert(i);
});

Only problem I can see with this is that I doubt the object equality comparison will only validate to true when the two DOM elements that are being compared are the exact same, but they could.

Moreover, though, what is the end goal that you are trying to accomplish here (as I'm sure making a page that harasses the user with alerts upon clicking any anchor is not your intended end goal).

MoarCodePlz
  • 5,086
  • 2
  • 25
  • 31
  • Thanks, can you provide any more detail? The way my brain works says is it because the reference to i is shared among all the functions because of the way scope chaining works? – nsfyn55 Jul 29 '11 at 16:51
  • I wish I could help you more in regards to variable scoping in Javascript but I'm no expert. I do know enough to say that it is relatively quirky compared to a number of other languages, but again if you have a better description as to what you're trying to do I may be able to help you. – MoarCodePlz Jul 29 '11 at 16:53
  • 1
    using a `for ... in` loop in JavaScript can lead to unexpected results. There's no guarantee it will loop through the items sequentially, and you also have the issue of dealing with inherited properties. A standard for loop would be better. This is also potentially expensive if the page has a lot of anchors. – Matt Greer Jul 29 '11 at 16:57
  • Yes I learned that the hard way. I didn't intend for this example to be used verbatim, only to serve as reference. – MoarCodePlz Jul 29 '11 at 16:58
0

The value of "i" when the loop ends is "4", so every when you click on a button the number that is going to show you is "4" because you are accessing the reference of "i" and the function is executing when you click on the button not during the loop.

If you want to get the number of "i" is the same as the button id, so i suggest doing this.

$(document).ready(function() {
//alert("ready");
var myClickFunctions = new Array();

//add event handlers to all three buttons
for(var i=1;i<=3;i++){

  myClickFunctions[i]=function(){
    var id = $(this).attr('id');
    var index = id.replace('butt', '');
    alert(index);
  }

  var buttonID = "#butt";
  var button  = $(buttonID + i);    
  button.click(myClickFunctions[i]);
}

});
RickyCheers
  • 334
  • 2
  • 6