Search code examples
javascriptscopeattachevent

Scope troubles in Javascript when passing an anonymous function to a named function with a local variable


Sorry about the title - I couldn't figure out a way to phrase it.

Here's the scenario:

I have a function that builds a element:

buildSelect(id,cbFunc,...)

Inside buildSelect it does this:

select.attachEvent('onchange',cbFunc);

I also have an array that goes:

var xs = ['x1','x2','x3'...];

Given all of these, I have some code that does this:

for(var i = 0; i < xs.length; i++)
{
    buildSelect(blah,function(){ CallBack(xs[i],...) },...);
}

The issue is that when onchange gets fired on one of those selects it correctly goes to CallBack() but the first parameter is incorrect. For example if I change the third select I expect CallBack() to be called with xs[2] instead I get some varying things like xs[3] or something else.

If I modify it slightly to this:

for(var i = 0; i < xs.length; i++)
{
    var xm = xs[i];
    buildSelect(blah,function(){ CallBack(xm,...) },...);
}

I'm still getting incorrect values in CallBack(). Something tells me this is scope/closure related but I can't seem to figure out what.

I simply want the first select to call CallBack for onchange with the first parameter as xs[0], the second select with xs[1] and so on. What could I be doing wrong here?

I should clarify that xs is a global variable.

Thanks


Solution

  • You need to capture that xm value by closing around it in its own scope.

    To do this requires a separate function call:

    buildCallback( curr_xm ) {
    
          // this function will refer to the `xm` member passed in
        return function(){ CallBack(curr_xm,...) },...);
    }
    
    for(var i = 0; i < xs.length; i++)
    {
        var xm = xs[ i ];
        buildSelect(blah,buildCallback( xm ),...);
    }
    

    Now the xm that the callback refers to is the one that you passed to buildCallback.

    If you have other uses for i that need to be retained, you could send that instead:

    buildCallback( curr_i ) {
    
    
          // this function will refer to the `i` value passed in
        return function(){ CallBack( xs[ curr_i ],...) },...);
    }
    
    for(var i = 0; i < xs.length; i++)
    {
        buildSelect(blah,buildCallback( i ),...);
    }