Проблемы с областями в Javascript при передаче анонимной функции в именованную функцию с локальной переменной

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

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

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

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], второй select с xs[1] и так далее. Что я могу здесь делать не так?

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

Спасибо

4 ответа

Решение

Вы должны захватить это 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 ),...);
}

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

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

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

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

Проблема действительно связана с областью видимости - 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]),...);
}

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

for(var i = 0; i < xs.length; i++)
{
    let xm = xs[i];
    buildSelect(blah,function(){ CallBack(xm,...) },...);
}
Другие вопросы по тегам