Skip to content

Loops don't behave like closures #3469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
farmdawgnation opened this issue May 6, 2014 · 5 comments
Closed

Loops don't behave like closures #3469

farmdawgnation opened this issue May 6, 2014 · 5 comments

Comments

@farmdawgnation
Copy link

While working on a project that also uses jQuery I noticed some severely unexpected behavior while using the for loop with jQuery click callbacks. After doing some investigating, I found that for loops don't behave as closures, which is something that I would expect as it is how they behave in numerous other languages.

To illustrate what I mean, take the following example. You have some markup:

<div>
    <button class="bacon-a">Bacon A</button>
    <button class="bacon-b">Bacon B</button>
    <button class="bacon-c">Bacon C</button>
</div>

and the following coffeescript:

bacons = ['a', 'b', 'c']

for bacon in bacons
  $(".bacon-" + bacon).click( ->
    alert(bacon)
  )

which produces this JavaScript:

var bacon, bacons, _i, _len;

bacons = ['a', 'b', 'c'];

for (_i = 0, _len = bacons.length; _i < _len; _i++) {
  bacon = bacons[_i];
  $(".bacon-" + bacon).click(function() {
    return alert(bacon);
  });
}

The expected behavior is that clicking the "Bacon A" button would yield an alert with "a" in it, clicking "Bacon B" would yield an alert with "b" in it, and so on. The actual result is that clicking any of the buttons yields an alert with "c" in it. You can see this behavior demonstrated in this jsfiddle example.

The solution seems to be changing the contents of the for loop to be wrapped in a self invoking function that takes in a parameter of the name you're looking for. Something like:

var bacons, _i, _len;

bacons = ['a', 'b', 'c'];

for (_i = 0, _len = bacons.length; _i < _len; _i++) {
  (function(bacon) {
    $(".bacon-" + bacon).click(function() {
      return alert(bacon);
    });
  })(bacons[_i]);
}

... and I confirmed that that appears to solve the problem.

@vendethiel
Copy link
Collaborator

The solution in Coffee is usually to use a do (val) ->

A syntax to allow to use both for and do in one line was proposed in #2518 - I'd really like to see that !

@farmdawgnation
Copy link
Author

May I ask what the usefulness of the existing implementation is?

@vendethiel
Copy link
Collaborator

It's just how JS (and most languages, really. Even though some are changing it, like C#) itself behaves. If I remember correctly, Coffee used to generate IIFEs for every loop, capturing every single variable in scope, but that was removed because of the inefficiency.

@farmdawgnation
Copy link
Author

:-/ Lame, but ok.

@vendethiel
Copy link
Collaborator

Feel free to leave your feedback on the answer I linked to say you'd benefit from do for a in b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants