Как назначить обратные вызовы событий, перебирая массив в javascript (jQuery) - PullRequest
4 голосов
/ 09 июля 2009

Я генерирую неупорядоченный список через javascript (используя jQuery). Каждый элемент списка должен получить свой собственный прослушиватель событий для события click. Однако у меня проблемы с получением правильного обратного вызова, прикрепленного к нужному предмету. (Раздетый) пример кода может немного прояснить ситуацию:

for(class_id in classes) {
    callback = function() { this.selectClass(class_id) };
    li_item = jQuery('<li></li>')
                .click(callback);
}

На самом деле, в этой итерации происходит больше, но я не думаю, что это было очень важно для вопроса. В любом случае происходит то, что функция обратного вызова, похоже, ссылается , а не хранится (и копируется). Конечный результат? Когда пользователь нажимает любой элементов списка, он всегда будет выполнять действие для last class_id в массиве classes, поскольку он использует функцию, сохраненную в callback в этот конкретный момент.

Я нашел грязные обходные пути (например, анализ атрибута href во вложенном элементе a), но мне было интересно, есть ли способ достичь моих целей «чистым» способом. Если мой подход ужасен, скажите, пожалуйста, если вы скажете мне почему :-) Спасибо!

Ответы [ 6 ]

8 голосов
/ 09 июля 2009

Это классическая проблема "вам нужно закрытие". Вот как это обычно происходит.

  1. Итерация по некоторым значениям
  2. Определить / назначить функцию в той итерации, которая использует итерированные переменные
  3. Вы узнаете, что каждая функция использует только значения из последней итерации.
  4. WTF

Опять же, когда вы видите этот паттерн, он должен сразу заставить вас думать "закрытие"

Расширяя свой пример, вот как бы вы закрыли

for ( class_id in classes )
{
  callback = function( cid )
  {
    return function()
    {
      $(this).selectClass( cid );
    }
  }( class_id );
  li_item = jQuery('<li></li>').click(callback);
}

Однако в этом конкретном экземпляре jQuery вам не нужно закрытие - но я должен спросить о природе вашей переменной classes - это объект? Потому что вы перебираете цикл for-in, который предлагает объект. И для меня напрашивается вопрос, почему вы не храните это в массиве? Потому что если бы ты был, твой код мог бы быть таким.

jQuery('<li></li>').click(function()
{
  $(this).addClass( classes.join( ' ' ) );
});
3 голосов
/ 09 июля 2009

Ваш код:

for(class_id in classes) {
    callback = function() { this.selectClass(class_id) };
    li_item = jQuery('<li></li>')
                        .click(callback);
}

Это в основном нормально, только одна проблема. Переменная callback является глобальной; поэтому каждый раз, когда вы зацикливаетесь, вы перезаписываете его. Поместите ключевое слово var перед ним для локального охвата, и все будет в порядке.

РЕДАКТИРОВАТЬ для комментариев: Это может быть не глобальным, как вы говорите, но это выходит за рамки цикла for. Таким образом, переменная является одной и той же ссылкой каждый раз в цикле. Помещение var в цикл ограничивает его циклом, каждый раз делая новую ссылку.

2 голосов
/ 09 июля 2009

Это лучший способ делать то, что вы хотите.

Добавьте информацию class_id в элемент, используя .data ().

Затем используйте .live (), чтобы добавить обработчик кликов ко всем новым элементам, чтобы избежать использования x * click.

for(class_id in classes) {
    li_item = jQuery('<li></li>').data('class_id', class_id).addClass('someClass');
}

//setup click handler on new li's
$('li.someClass').live('click', myFunction )

function myFunction(){
   //get class_id
   var classId = $(this).data('class_id');
   //do something
}
1 голос
/ 09 июля 2009

Мой javascript fu довольно слабый, но, насколько я понимаю, он закрывает ссылки на локальные переменные в стеке (и этот кадр стека передается функцией, опять же, очень схематично). Ваш пример действительно не работает, потому что каждая функция хранит ссылку на одну и ту же переменную. Вместо этого попробуйте создать другую функцию, которая создает замыкание, т.е.

function createClosure(class_id) {
  callback = function() { this.selectClass(class_id) };
  return callback;
}

и затем:

for(class_id in classes) {
  callback = createClosure(class_id);
  li_item = jQuery('<li></li>').click(callback);
}

Конечно, это немного круто, возможно, есть и лучшие способы.

1 голос
/ 09 июля 2009

почему вы не можете сгенерировать их все, а затем вызвать что-то вроде

$(".li_class").click(function(){ this.whatever() };

EDIT:

Если вам нужно добавить больше классов, просто создайте в цикле строку со всеми именами классов и используйте ее в качестве селектора.

$(".li_class1, .li_class2, etc").click(function(){ this.whatever() };
0 голосов
/ 09 июля 2009

Или вы можете прикрепить class_id к .data () этих элементов списка.

$("<li />").data("class_id", class_id).click(function(){
    alert("This item has class_id "+$(this).data("class_id"));
});

Будьте осторожны: вы создаете функцию обратного вызова заново для каждого $("<li />") вызова. Я не уверен в деталях реализации JavaScript, но это может быть дорого. Вместо этого вы можете сделать

function listItemCallback(){
     alert("This item has class_id "+$(this).data("class_id"));
}

$("<li />").data("class_id", class_id).click(listItemCallback);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...