0

I'm having a tough time understanding and using closures (yes, I have read How do JavaScript closures work?)

My problem is as follows:

for (row = 0; row < 10; row++) {
    for (column = 0; column < 10; column++) {

        var target = $("#" + Data.Row[row].Column[column].ID);

        target.mouseenter(function () {
            var position = CalculatePosition($(this));

            alert("row:" + row + ",column:" + column);

            ...
        });
    }
}

As you might expect, row and column is always 9 whenever target has the mouse over it. My question is then, how can I freeze the value of row and column so that the mouseevent anonymous function can get their intended values? I tried doing something like

target.mouseenter(function() {}.bind(column));

And that seems to work for just column, but then of course this is no longer referring to target.

Community
  • 1
  • 1
Technolar
  • 15
  • 4
  • Nah, [`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) does something different which you can't use here (also read [about the `this` keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)) – Bergi Jul 22 '13 at 22:31
  • @Bergi He can use `bind` like: `function(row, column){}.bind(target, row, column)`. – Paul Jul 22 '13 at 22:32
  • I suspect you want the function to know which element was clicked and each element has a different ID. True? – Lee Meador Jul 22 '13 at 22:32
  • @Paulpro: Ah, didn't see that he knows `this`/`target[0]` outside of the handler already. Wouldn't have worked for larger collections at least… – Bergi Jul 22 '13 at 22:34

2 Answers2

5

The easiest option here is generally to define a function that returns your handler:

function getHandler(row, column)
    return function () {
        var position = CalculatePosition($(this));
        alert("row:" + row + ",column:" + column);
        // ...
    };
}

Then call this function in your loop to get your handler with the relevant variables "fixed" to their values at call time:

for (row = 0; row < 10; row++) {
    for (column = 0; column < 10; column++) {
        var target = $("#" + Data.Row[row].Column[column].ID);
        target.mouseenter(getHandler(row, column));
    }
}

You can also do this within the loop, in an immediately executing anonymous function:

for (row = 0; row < 10; row++) {
    for (column = 0; column < 10; column++) {
        var target = $("#" + Data.Row[row].Column[column].ID);
        target.mouseenter((function(row, column) {
            return function () {
                var position = CalculatePosition($(this));

                alert("row:" + row + ",column:" + column);

                ...
            };
        })(row, column)));
    }
}

But IMO that's a lot uglier and harder to read.

In either case, the basic approach here is to establish a new function scope, using the loop variables as arguments; now when you use them in the handler callback, they're no longer references to outer-scope variables.

nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • 1
    +1 This works by creating a new closure for each iteration through the loops and that closure contains the mouse enter handler too. That way the handler for each element has its own stored x and y values. – Lee Meador Jul 22 '13 at 22:35
  • Thanks! The solution makes a lot more sense in my head now. – Technolar Jul 22 '13 at 22:43
0

You can use bind like so (the first argument is the context (this arg)):

target.mouseenter(function(row, column){
    ...
}.bind(target[0], row, column));

But bind is not cross browser, and you are already using jQuery, so you should use proxy instead:

target.mouseenter($.proxy(function(row, column){
    ...
}, target[0], row, column));
Paul
  • 139,544
  • 27
  • 275
  • 264
  • Yet you should use `target[0]` to get the DOM element only, or remove `$()` from `this` inside the handler. – Bergi Jul 22 '13 at 22:36
  • @Bergi Thanks, I didn't notice that `target` was a jQuery object (I assumed it was `e.target` from some event handler). I've updated it to use `target[0]`. – Paul Jul 22 '13 at 22:37