Проблемы с областью в Javascript при передаче анонимной функции в именованную функцию с локальной переменной - PullRequest
4 голосов
/ 17 февраля 2011

Извините за название - я не смог придумать, как его сформулировать.

Вот сценарий:

У меня есть функция, которая создает элемент:

buildSelect(id,cbFunc,...)

Внутри buildSelect он делает это:

select.attachEvent('onchange',cbFunc);

У меня также есть массив, который идет:

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

Учитывая все это, у меня есть код, который делает это:

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

Проблема в том, что когда onchange запускается на одном из этих переключателей, он корректно переходит к CallBack (), но первый параметр неверен. Например, если я изменю третий выбор, я ожидаю, что CallBack () будет вызываться с помощью xs [2], вместо этого я получаю некоторые различные вещи, такие как xs [3] или что-то еще.

Если я немного изменю это:

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

Я все еще получаю неправильные значения в CallBack (). Что-то говорит мне, что это связано с областью действия / замыканием, но я не могу понять, что именно.

Я просто хочу, чтобы первый выбор вызывал CallBack для onchange с первым параметром как xs [0], вторым выбором с xs [1] и так далее. Что я могу здесь делать не так?

Я должен уточнить, что xs является глобальной переменной.

Спасибо

Ответы [ 4 ]

4 голосов
/ 17 февраля 2011

Да, я думаю, что закрытие поможет:

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

Редактировать: Я также немного оптимизировал ваш цикл for.

Редактировать: Полагаю, я добавлю объяснение. Что вы делаете, это создаете анонимную функцию, которая принимает один аргумент (xm) и сразу же вызываете функцию (с круглыми скобками сразу после). Эта анонимная функция также должна возвращать вашу исходную функцию в качестве аргумента buildSelect ().

4 голосов
/ 17 февраля 2011

Проблема действительно связана с областью действия - JavaScript имеет только область действия функции, а не область блока или область цикла. Существует только один экземпляр переменных i и xm, и значение этих переменных изменяется по мере прохождения цикла. Когда цикл завершен, у вас остается только последнее значение, которое они содержали. Ваши анонимные функции захватывают сами переменные, а не их значения.

Чтобы получить фактическое значение переменной, вам нужна другая функция, в которой вы можете захватить локальную переменную:

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

Каждый вызов makeCallback получает новый экземпляр переменной value, и если вы захватываете эту переменную, вы по существу захватываете значение:

for(var i = 0; i < xs.length; i++)
{
    buildSelect(blah,makeCallback(xs[i]),...);
}
4 голосов
/ 17 февраля 2011

Вам нужно захватить это значение xm, закрыв его в своей области видимости.

Для этого требуется отдельный вызов функции:

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

Теперь xmобратный вызов относится к тому, который вы передали buildCallback.

Если у вас есть другие варианты использования i, которые необходимо сохранить, вы можете отправить это вместо:

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 ),...);
}
0 голосов
/ 12 марта 2018

Очевидно, есть новое ключевое слово let, которое делает то, что вы хотите:

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