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

夙愿已清 提交于 2019-12-20 01:35:21

问题


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


回答1:


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 ),...);
}



回答2:


The problem is indeed scope-related -- JavaScript has only function scope, not block scope or loop scope. There is only a single instance of the variables i and xm, and the value of these variables changes as the loop progresses. When the loop is done, you're left with only the last value that they held. Your anonymous functions capture the variables themselves, not their values.

To capture the actual value of a variable, you need another function where you can capture the local variable:

function makeCallback(value) {
  return function() { CallBack(value, ...) };
}

Each call to makeCallback gets a new instance of the value variable and if you capture this variable, you essentially capture the value:

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



回答3:


Yes, I think a closure would help:

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

Edit: I also optimised your for loop slightly.

Edit: I guess I'll add an explanation. What you're doing is creating an anonymous function which takes one argument (xm) and calling the function straight away (with the parenthesis right after). This anonymous function must also return your original function as an argument of buildSelect().




回答4:


Apparently there is a new let keyword that does what you want:

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


来源:https://stackoverflow.com/questions/5024882/scope-troubles-in-javascript-when-passing-an-anonymous-function-to-a-named-funct

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!