JavaScript не имеет области видимости блока, только область видимости функции (ну и глобальная область видимости). Замыкания получают прямую ссылку на переменную, и поэтому ваша функция обработчика событий (которая является замыканием) всегда будет видеть последнее значение, присвоенное dialog_button
.
Лучшая ставка в описываемой вами конкретной ситуации - использовать функцию $.each
в jQuery, а не for
loop (спасибо @Esailija и обратите внимание, что Tadeck предложил $.each
до того, как я сделал - стоит повышенного голоса - я добавил его по предложению @ Esailija, потому что это, безусловно, лучшее решение в данной конкретной ситуации) :
$.each(dialogs, function(index, dialog_button) {
var ix_parts = $(dialog_button).attr("id").split("_");
var index_tag = ix_parts[1];
var dialog_panel = $(dialog_panel_selector.replace("$ix$", index_tag));
$(dialog_button).click(function (event) {
$(dialog_panel).dialog('open');
return false;
});
});
... потому что теперь каждый вызов функции, которую мы передаем $.each
, получает собственный уникальный аргумент dialog_button
, поэтому каждая сгенерированная функция (замыкание) закрывается над своей собственной копией, и мы не сталкиваемся с проблема новых значений, присваиваемых переменной.
Я предложил функцию jQuery, потому что вы уже используете jQuery, и поэтому она наверняка доступна. Начиная с ECMAScript5, есть встроенная функция Array#forEach
, которая делает то же самое, но пока не у всех двигателей.
В ситуациях, когда вышеперечисленное не отвечает всем требованиям, вот еще один способ. Он также включает в себя довольно глубокое обсуждение того, что происходит, почему и как это контролировать в ваших интересах:
Лучше всего использовать функцию, которая создает обработчики событий, например, так (я полагаю, все это внутри функции):
for (var i = 0; i < dialogs.length; i++) {
var dialog_button = dialogs[i];
var ix_parts = $(dialog_button).attr("id").split("_");
var index_tag = ix_parts[1];
var dialog_panel = $(dialog_panel_selector.replace("$ix$", index_tag));
$(dialog_button).click(createHandler(dialog_button));
}
function createHandler(dlg) {
return function (event) {
$(dlg).dialog('open');
return false;
};
}
Там цикл вызывает createHandler
, что создает функцию-обработчик как замыкание в контексте вызова createHandler
, поэтому обработчик ссылается на dlg
. Каждый вызов createHandler
получит свой уникальный контекст и, следовательно, собственный уникальный аргумент dlg
. Таким образом, замыкания относятся к ожидаемым значениям.
Вы можете расположить createHandler
внутри вашей общей функции, если хотите (убедитесь, что она не находится внутри каких-либо ветвей, она должна быть на верхнем уровне функции), например:
function createDialogs() {
for (var i = 0; i < dialogs.length; i++) {
var dialog_button = dialogs[i];
var ix_parts = $(dialog_button).attr("id").split("_");
var index_tag = ix_parts[1];
var dialog_panel = $(dialog_panel_selector.replace("$ix$", index_tag));
$(dialog_button).click(createHandler(dialog_button));
}
function createHandler(dlg) {
return function (event) {
$(dlg).dialog('open');
return false;
};
}
}
... или если вам нужно сделать то же самое в другом месте, вы можете переместить его на уровень:
function createDialogs() {
for (var i = 0; i < dialogs.length; i++) {
var dialog_button = dialogs[i];
var ix_parts = $(dialog_button).attr("id").split("_");
var index_tag = ix_parts[1];
var dialog_panel = $(dialog_panel_selector.replace("$ix$", index_tag));
$(dialog_button).click(createHandler(dialog_button));
}
}
function createHandler(dlg) {
return function (event) {
$(dlg).dialog('open');
return false;
};
}
Преимущество для последнего в том, что он означает, что небольшой блок памяти (называемый объект привязки переменной ), созданный при каждом вызове createDialogs
, ни на что не ссылается и может быть очищен при вызове возвращает, тогда как с первым (где createHandler
находится в пределах createDialogs
), на эту память ссылаются объекты привязки переменных для вызовов к createHandler
, и поэтому не подходит для очистки. У каждого есть свои применения, это зависит только от того, нужен ли вам доступ к чему-либо в контексте вызова createDialog
(в коде, который вы показали, вы этого не делаете, но я понимаю, что это всего лишь отрывок).
Подробнее:
В комментариях вы спрашивали:
"(убедитесь, что он не находится внутри каких-либо веток, он должен находиться на верхнем уровне функции)" Не могли бы вы уточнить это? Я объявил функцию внутри цикла for, и она, похоже, работает! Я что-то не так делаю?
JavaScript имеет две различные конструкции function
: функция объявления и функция выражения . Синтаксически они очень похожи, но они допустимы в разных местах и происходят в разное время.
TL; DR: My createHandler
является примером функции объявление . Они не могут быть внутри контрольных структур. Ваша function
конструкция, которую вы передаете в click
, является функцией expression , которая может быть. Разница в том, является ли конструкция function
значением для правой руки или нет (у меня нет, у вас есть). Правильное понимание объявлений и выражений является ключом к умелому программированию на JavaScript.
Длинная версия:
Вот функция объявление :
function foo() {
}
Вот функция выражение , назначаемое переменной:
var foo = function() {
};
Вот еще одно выражение функции, на этот раз используемое как инициализатор свойства в литерале объекта:
var obj = {
foo: function() {
}
};
И еще одно выражение функции, на этот раз передаваемое в функцию в качестве аргумента:
bar(function() {
});
Как видите, различие между ними заключается в том, что объявления функций стоят отдельно, тогда как выражение функции используется как правое значение в содержащем выражении & mdash; в качестве правой части присваивания (=
) или инициализатора (:
) или переданного в функцию в качестве аргумента.
Функция объявления обрабатываются при создании содержащей их области действия перед выполнением любого пошагового кода. Итак, учитывая:
function bar() {
function foo() {
}
return foo;
}
... когда вызывается bar
, перед выполнением любого пошагового кода создается функция foo
. Только после этого запускается пошаговый код, который в этом случае возвращает ссылку на функцию foo
. Следовательно, вышеприведенное в точности эквивалентно этому:
function bar() {
return foo;
function foo() {
}
}
Хотя это выглядит так, как будто foo
не должно существовать с оператором return
, оно существует.
Поскольку объявления функций выполняются перед пошаговым кодом, они не могут находиться внутри операторов потока управления:
function bar(condition) {
if (condition) {
function foo() { // <== INVALID
return alert("A");
}
}
else {
function foo() { // <== INVALID
return alert("B");
}
}
return foo;
}
var f = bar(true);
f(); // alerts what?
Вы бы подумали, что в предупреждении будет написано "А", верно? Потому что мы передали true
для condition
, и поэтому происходит первая ветвь. Но это не то, что происходит вообще. Технически, вышесказанное является синтаксической ошибкой, чистой и простой. Но большинство браузеров не рассматривают его как один (движок Firefox [SpiderMonkey] - единственный, который мне известен). Так что они делают? Это зависит. Большинство механизмов продолжают обрабатывать их как объявления функций, и когда у вас есть два объявления функций для одной и той же функции в одной и той же области видимости, в спецификации говорится, что выигрывает второй. Таким образом, эти двигатели будут предупреждать "B". Но другие механизмы (IE один) переписывают ваш код на лету, превращая эти объявления в выражения, и поэтому эти механизмы будут предупреждать «A». Здесь Там Пока Драконы. Не делай этого. : -)
Функция выражений , с другой стороны, создает функции как часть пошагового кода. Они происходят, когда исполнение достигает их в потоке. Так что это очень отличается от предыдущего примера:
function bar() {
var foo;
return foo;
foo = function() {
};
}
Здесь bar
возвращает undefined
, поскольку для оператора return
foo
равно undefined
и, конечно, следующий оператор присваивания никогда не встречается. Точно так же это верно и его поведение четко определено:
function bar(condition) {
var foo;
if (condition) {
foo = function() {
return alert("A");
};
}
else {
foo = function() {
return alert("B");
};
}
return foo;
}
var f = bar(true);
f(); // alerts "A"
f = bar(false);
f(); // alerts "B"
Поскольку теперь мы используем функциональные выражения, они появляются в виде пошагового кода, и поведение выглядит так, как должно быть.
Возвращение всего этого к вашему конкретному примеру. В целом, создание функций в циклах - плохая идея, но бывают случаи, когда это необходимо. В таких случаях обычно вам нужен помощник, такой как моя createHandler
функция, которая находится вне цикла, так что вы лучше контролируете контекст. Вы могли бы сделать это:
for (var i = 0; i < dialogs.length; i++) {
var dialog_button = dialogs[i];
var ix_parts = $(dialog_button).attr("id").split("_");
var index_tag = ix_parts[1];
var dialog_panel = $(dialog_panel_selector.replace("$ix$", index_tag));
$(dialog_button).click((function(dlg) {
return function (event) {
$(dlg).dialog('open');
return false;
};
})(dialog_button));
}
... но это очень плохая идея. Во-первых, это трудно читать. Во-вторых, вы создаете дополнительные функции: каждая итерация цикла фактически создает два функциональных объекта, один из которых мы используем для создания другого (например, тот, который был createHandler
), и тот, который он создает. Поэтому, если есть три диалоговых окна, вы создаете шесть функций вместо трех, и все эти функции остаются до тех пор, пока не будут удалены обработчики.
И последнее замечание: все приведенные выше выражения функций создали анонимные функции (функции без имен). Я не люблю анонимные функции ; назначение имен функций помогает вашим инструментам. Технически законно давать им имена:
var f = function foo() { // <== WARNING, see below
};
... но вы не можете сделать это в дикой природе в настоящее время из-за ошибок в IE до IE9 .IE8 и ниже увидят эту конструкцию дважды, один раз как объявление функции, затем снова как выражение функции.Это действительно создаст два функциональных объекта, которые могут вызвать всевозможные проблемы.: -)