简体   繁体   中英

Issue with Coffeescript comprehensions and closures

I ran into a problem when converting some JavaScript to CoffeeScript. The basic issue is I went from using $.each to CoffeeScript comprehensions and I now have a problem where the last value of the comprehension is being captured in closures. Lets start with the original JavaScript:

function bindKeyboardControls(websocket) {
    var moveKeyMap = {
        Down: ['down', 's'],
        Up: ['up', 'w'],
        Left: ['left', 'a'],
        Right: ['right', 'd']
    };

    $.each(moveKeyMap, function (direction, keys) {
        $.each(keys, function (_, key) {
            $(document).bind('keydown', key, function () { move(websocket, direction); });
        });
    });
};

Here is my first attempt with CoffeeScript:

    bindKeyboardControls = (websocket) ->
    moveKeyMap =
        Down: ['down', 's']
        Up: ['up', 'w']
        Left: ['left', 'a']
        Right: ['right', 'd']        
    for direction, keys of moveKeyMap
        for key in keys
            $(document).bind('keydown', key, -> move(websocket, direction))
    null

Why doesn't this work? Well here's the generated JavaScript:

bindKeyboardControls = function(websocket) {
  var direction, key, keys, moveKeyMap, _i, _len;
  moveKeyMap = {
    Down: ['down', 's'],
    Up: ['up', 'w'],
    Left: ['left', 'a'],
    Right: ['right', 'd']
  };
  for (direction in moveKeyMap) {
    keys = moveKeyMap[direction];
    for (_i = 0, _len = keys.length; _i < _len; _i++) {
      key = keys[_i];
      $(document).bind('keydown', key, function() {
        return move(websocket, direction);
      });
    }
  }
  return null;
};

Do you see how the 'direction' variable is declared at the top of the function? The function that is being passed in to document.bind is creating a closure on that variable, so by the time the function runs, direction will always be equal to the last value ('Right').

Here is a fixed version that is somewhat ugly:

    bindKeyboardControls = (websocket) ->
    moveKeyMap =
        Down: ['down', 's']
        Up: ['up', 'w']
        Left: ['left', 'a']
        Right: ['right', 'd']        
    for direction, keys of moveKeyMap
        for key in keys                             
            ((d) -> $(document).bind('keydown', key, -> move(websocket, d)))(direction)
    null        

I could also just go back to using $.each. So I do have some solutions, but is there a better one?

Yes:

for direction, keys of moveKeyMap
  for key in keys
    do (direction, key) -> $(document).bind('keydown', key, -> move(websocket, d))

That creates and runs an anonymous function that captures the values of direction and key , so that the the bind callback can use them. See my PragProg article A CoffeeScript Intervention for more on this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM