4

我正在通过javascript(使用jQuery)生成一个无序列表。每个列表项必须接收自己的“点击”事件监听器。但是,我无法将正确的回调附加到正确的项目。一个(剥离的)代码示例可能会让事情变得更清楚:

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

实际上,在这个迭代中还有更多的事情发生,但我认为它与这个问题不太相关。无论如何,发生的事情是回调函数似乎被引用而不是存储(&复制)。最终结果?当用户单击任何列表项时,它将始终执行数组中最后一个 class_id的操作classes,因为它使用存储在callback该特定点的函数。

我发现了肮脏的解决方法(例如解析href封闭a元素中的属性),但我想知道是否有办法以“干净”的方式实现我的目标。如果我的方法很可怕,请说出来,只要你告诉我原因:-) 谢谢!

4

6 回答 6

8

这是一个经典的“你需要一个闭包”问题。以下是它通常的表现。

  1. 迭代一些值
  2. 在该迭代中定义/分配一个使用迭代变量的函数
  3. 您了解到每个函数仅使用上一次迭代中的值。
  4. 怎么回事?

同样,当您看到这种模式时,它应该立即让您想到“关闭”

扩展您的示例,这是您如何放入闭包

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( ' ' ) );
});
于 2009-07-09T14:59:51.187 回答
3

你的代码:

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

这大多没问题,只是一个问题。变量callback是全局的;所以每次循环时,你都会覆盖它。将var关键字放在它前面以在本地范围内,你应该没问题。

编辑评论:它可能不像你说的那样是全局的,但它超出了 for 循环的范围。因此,每次循环时,变量都是相同的引用。放入var循环将其范围限定为循环,每次都创建一个新的引用。

于 2009-07-09T15:04:35.250 回答
2

这是做你想做的更好的清洁方式。

使用 .data() 将 class_id 信息添加到元素上。

然后使用 .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
}
于 2009-07-09T15:03:27.123 回答
1

为什么你不能全部生成它们然后调用类似的东西

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

编辑:

如果您需要添加更多类,只需在循环中创建一个包含所有类名的字符串并将其用作您的选择器。

$(".li_class1, .li_class2, etc").click(function(){ this.whatever() };
于 2009-07-09T14:46:58.700 回答
1

我的 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);
}

当然,这有点杂乱无章,可能有更好的方法。

于 2009-07-09T14:58:45.603 回答
0

或者,您可以将 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);
于 2009-07-09T15:04:08.010 回答