Странные вещи в JavaScript для

Я использую jQuery, и у меня есть странная вещь, которую я не понимаю. У меня есть код:

for (i = 1; i <= some_number; i++) {
    $("#some_button" + i).click(function() {
        alert(i);
    });
}

"#some_button" как следует из названия - это какие-то кнопки. При нажатии они должны выскакивать окно с его номером, верно? Но они этого не делают. Если есть 4 кнопки, они всегда выскакивают «5» (количество кнопок + 1). Почему это так?


person dmitq    schedule 30.06.2010    source источник
comment
Как следует из большинства ответов, это очень распространенная проблема. Кроме того, все вопросы с тегами javascript, closures, и loops будут указывать именно на эту проблему и решения - javascript + closures + loops, включая этот сейчас :)   -  person Anurag    schedule 30.06.2010
comment
нам нужен способ для SO, чтобы появилось огромное всплывающее окно с сообщением, что НА ЭТО ОТВЕТИЛИ ПРЕЖДЕ ЧЕМ, со ссылками на правильный вопрос. Я видел как минимум 3 версии javascript и 2 версии python, задающие этот вопрос.   -  person Claudiu    schedule 30.06.2010
comment
@Claudiu: К сожалению, новичкам в JavaScript / Python очень сложно искать подобные проблемы.   -  person Daniel Vassallo    schedule 30.06.2010
comment
Связано: stackoverflow.com/questions/227820 stackoverflow.com/questions/512166   -  person Josh Lee    schedule 08.07.2010


Ответы (6)


Это связано с областью видимости JavaScript. Вы можете легко обойти это, введя другую область видимости, добавив функцию и заставив эту функцию вызвать себя и передать i:

for (var i = 1; i <= some_number; i++) {
  (function(j) {
    $("#some_button" + j).click(function() {
      alert(j);
    });
  })(i);
}

Это создает закрытие - когда внутренняя функция имеет доступ к области, которая больше не существует, когда функция вызывается. Дополнительную информацию см. В этой статье о MDC.

РЕДАКТИРОВАТЬ: RE: Самовызывающиеся функции: самовызывающая функция - это функция, которая вызывает себя анонимно. Вы не создаете его экземпляр и не присваиваете его переменной. Он принимает следующую форму (обратите внимание на открывающие скобки):

(function(args) {
  // function body that might modify args
})(args_to_pass_in);

В отношении этого вопроса тело анонимной функции будет выглядеть следующим образом:

$("#some_button" + j).click(function() {
  alert(j);
});

Объединив их вместе, мы получим ответ в первом блоке кода. Анонимная функция самовызова ожидает аргумент с именем j. Он ищет любой элемент с идентификатором some_button с целочисленным значением j в конце (например, some_button1, some_button10). Каждый раз, когда щелкают по одному из этих элементов, отображается значение j. Предпоследняя строка решения передает значение i, которое является счетчиком цикла, в котором вызывается анонимная функция самовызова. Если сделать по-другому, это может выглядеть так:

var innerFunction = function(j) {
  $("#some_button" + j).click(function() {
    alert(j);
  });
};

for (var i = 1; i <= some_number; i++) {
  innerFunction(i);
}
person Hooray Im Helping    schedule 30.06.2010
comment
Боже мой :( Не могли бы вы, пожалуйста, переписать его в более удобочитаемой форме? Никто, кто еще не знает ответа, никогда не поймет ничего из написанного вами. );});})(i);} ... - person SF.; 30.06.2010
comment
@SF: это в значительной степени то, что вам нужно сделать .. Может быть, это поможет: вы превращаете for (...) { LOGIC(i); }, где LOGIC - произвольный код, работающий с переменной i, в for (...) { (FUNC)(i) }, немедленно создавая функцию и передавая ей i. FUNC просто function (j) { LOGIC(j); }. - person Claudiu; 30.06.2010
comment
Да, вы можете рассматривать замыкание как функцию, которая имеет ссылку на каждую используемую внешнюю переменную, а не как копию. Эта ошибка возникла из-за того, что я использовался по ссылке внутри закрытия. Это исправление решает проблему путем копирования значения в j - путем передачи его в функцию в качестве аргумента. - person Chris Moschini; 28.12.2011

У вас очень распространенная проблема с закрытием в for1. .

Переменные, заключенные в замыкание, используют одну и ту же единую среду, поэтому к моменту вызова обратного вызова click цикл завершится, и переменная i останется указывающей на последнюю запись.

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

function makeOnClickCallback(i) {  
   return function() {  
      alert(i);
   };  
} 

var i;

for (i = 0; i < some_number; i++) {
    $("#some_button" + i).click(makeOnClickCallback(i));
}

Это может быть довольно сложной темой, если вы не знакомы с тем, как работают замыкания. Вы можете ознакомиться со следующей статьей Mozilla для краткого введения:

person Daniel Vassallo    schedule 30.06.2010

Потому что в тот момент, когда вы нажимаете на них, i == 5.

person Matteo Mosca    schedule 30.06.2010
comment
хе очень лаконично, но это поможет только тому, кто уже знает, в чем проблема - person Claudiu; 30.06.2010
comment
Ого, только что получил голосование через 6 месяцев ... в любом случае, OP действительно нужно было изучить концепцию закрытия, а затем больше никаких загадок. - person Matteo Mosca; 27.12.2011

Это связано с тем, как замыкания работают в JavaScript. Каждая из 5 функций, которые вы создаете, в основном использует одну и ту же переменную i. Значение i внутри вашей функции оценивается не при создании функции, а при возникновении события щелчка, когда значение i равно 5.

Есть различные способы обойти это (когда такое поведение не то, что вам нужно). Один (если у вас есть простая функция, как здесь) - использовать конструктор функции вместо литерала функции:

$("#some_button" + i).click(new Function("alert("+i+")");
person jhurshman    schedule 30.06.2010

  (function (some_number) {
    for (i = 1; i <= some_number; i++) {
      $("#some_button" + i).click(function() {
        alert(i);
      });
    }
  })(some_number);

Оберните функцию снаружи, потому что для скорости и того факта, что я буду продолжать сбрасывать.

person RobertPitt    schedule 30.06.2010

Это очень умный код. Так умно это вопрос по ТАК. :) Я бы вообще обошел этот вопрос, сделав код глупее, просто чтобы иметь шанс понять его (или дать понять его коллеге) через шесть месяцев. Замыкания имеют свое место, но в этом случае я бы избегал их в пользу более понятного кода.

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

person Ed Daniel    schedule 30.06.2010
comment
Что лучше: иметь сложный код для решения проблемы или вообще избежать проблемы? - person Ed Daniel; 01.07.2010