Проблемы с областями в 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,...) },...);
}