如何在javascript(jQuery)中分配迭代数组的事件回调

时间:2021-10-08 02:41:58

I'm generating an unordered list through javascript (using jQuery). Each listitem must receive its own event listener for the 'click'-event. However, I'm having trouble getting the right callback attached to the right item. A (stripped) code sample might clear things up a bit:

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

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

Actually, more is going on in this iteration, but I didn't think it was very relevant to the question. In any case, what's happening is that the callback function seems to be referenced rather than stored (& copied). End result? When a user clicks any of the list items, it will always execute the action for the last class_id in the classes array, as it uses the function stored in callback at that specific point.

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

I found dirty workarounds (such as parsing the href attribute in an enclosed a element), but I was wondering whether there is a way to achieve my goals in a 'clean' way. If my approach is horrifying, please say so, as long as you tell me why :-) Thanks!

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

6 个解决方案

#1


This is a better cleaner way of doing what you want.

这是一种更干净的方式来做你想做的事情。

Add the class_id info onto the element using .data().

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

Then use .live() to add a click handler to all the new elements, this avoids having x * click functions.

然后使用.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
}

#2


This is a classic "you need a closure" problem. Here's how it usually plays out.

这是一个经典的“你需要一个关闭”的问题。这是它通常如何发挥作用。

  1. Iterate over some values
  2. 迭代一些值

  3. Define/assign a function in that iteration that uses iterated variables
  4. 在使用迭代变量的迭代中定义/分配函数

  5. You learn that every function uses only values from the last iteration.
  6. 您了解到每个函数仅使用上一次迭代中的值。

  7. WTF?

Again, when you see this pattern, it should immediately make you think "closure"

再一次,当你看到这种模式时,它会立即让你想到“封闭”

Extending your example, here's how you'd put in a closure

扩展你的例子,这是你如何放入一个闭包

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

However, in this specific instance of jQuery, you shouldn't need a closure - but I have to ask about the nature of your variable classes - is that an object? Because you iterate over with a for-in loop, which suggest object. And for me it begs the question, why aren't you storing this in an array? Because if you were, your code could just be this.

但是,在jQuery的这个特定实例中,你不应该需要一个闭包 - 但我必须询问你的变量类的性质 - 是一个对象吗?因为您使用for-in循环进行迭代,这表示对象。对我而言,它引出了一个问题,为什么不将它存储在一个数组中呢?因为如果你是,你的代码可能就是这样。

jQuery('<li></li>').click(function()
{
  $(this).addClass( classes.join( ' ' ) );
});

#3


Your code:

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

This is mostly ok, just one problem. The variable callback is global; so every time you loop, you are overwriting it. Put the var keyword in front of it to scope it locally and you should be fine.

这基本上没问题,只有一个问题。变量回调是全局的;所以每次你循环,你都会覆盖它。把var关键字放在它前面以在本地范围内,你应该没问题。

EDIT for comments: It might not be global as you say, but it's outside the scope of the for-loop. So the variable is the same reference each time round the loop. Putting var in the loop scopes it to the loop, making a new reference each time.

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

#4


My javascript fu is pretty weak but as I understand it closures reference local variables on the stack (and that stack frame is passed around with the function, again, very sketchy). Your example indeed doesn't work because each function keeps a reference to the same variable. Try instead creating a different function that creates the closure i.e.:

我的javascript fu非常弱,但据我所知,闭包引用了堆栈上的局部变量(并且该堆栈帧与函数一起传递,同样非常粗略)。您的示例确实不起作用,因为每个函数都保留对同一变量的引用。尝试创建一个创建闭合的不同函数,即:

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

and then:

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

It's a bit of a kludge of course, there's probably better ways.

当然,这有点像一块垃圾,可能有更好的方法。

#5


why can't you generate them all and then call something like

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

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

EDIT:

If you need to add more classes, just create a string in your loop with all the class names and use that as your selector.

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

$(".li_class1, .li_class2, etc").click(function(){ this.whatever() };

#6


Or you can attach the class_id to the .data() of those list items.

或者,您可以将class_id附加到这些列表项的.data()。

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

Be careful, though: You're creating the callback function anew for every $("<li />") call. I'm not sure about JavaScript implementation details, but this might be memory expensive. Instead, you could do

但要小心:您正在为每个$(“

  • ”)调用重新创建回调函数。我不确定JavaScript实现细节,但这可能是内存昂贵的。相反,你可以做到

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


    This is a better cleaner way of doing what you want.

    这是一种更干净的方式来做你想做的事情。

    Add the class_id info onto the element using .data().

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

    Then use .live() to add a click handler to all the new elements, this avoids having x * click functions.

    然后使用.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
    }
    

    #2


    This is a classic "you need a closure" problem. Here's how it usually plays out.

    这是一个经典的“你需要一个关闭”的问题。这是它通常如何发挥作用。

    1. Iterate over some values
    2. 迭代一些值

    3. Define/assign a function in that iteration that uses iterated variables
    4. 在使用迭代变量的迭代中定义/分配函数

    5. You learn that every function uses only values from the last iteration.
    6. 您了解到每个函数仅使用上一次迭代中的值。

    7. WTF?

    Again, when you see this pattern, it should immediately make you think "closure"

    再一次,当你看到这种模式时,它会立即让你想到“封闭”

    Extending your example, here's how you'd put in a closure

    扩展你的例子,这是你如何放入一个闭包

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

    However, in this specific instance of jQuery, you shouldn't need a closure - but I have to ask about the nature of your variable classes - is that an object? Because you iterate over with a for-in loop, which suggest object. And for me it begs the question, why aren't you storing this in an array? Because if you were, your code could just be this.

    但是,在jQuery的这个特定实例中,你不应该需要一个闭包 - 但我必须询问你的变量类的性质 - 是一个对象吗?因为您使用for-in循环进行迭代,这表示对象。对我而言,它引出了一个问题,为什么不将它存储在一个数组中呢?因为如果你是,你的代码可能就是这样。

    jQuery('<li></li>').click(function()
    {
      $(this).addClass( classes.join( ' ' ) );
    });
    

    #3


    Your code:

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

    This is mostly ok, just one problem. The variable callback is global; so every time you loop, you are overwriting it. Put the var keyword in front of it to scope it locally and you should be fine.

    这基本上没问题,只有一个问题。变量回调是全局的;所以每次你循环,你都会覆盖它。把var关键字放在它前面以在本地范围内,你应该没问题。

    EDIT for comments: It might not be global as you say, but it's outside the scope of the for-loop. So the variable is the same reference each time round the loop. Putting var in the loop scopes it to the loop, making a new reference each time.

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

    #4


    My javascript fu is pretty weak but as I understand it closures reference local variables on the stack (and that stack frame is passed around with the function, again, very sketchy). Your example indeed doesn't work because each function keeps a reference to the same variable. Try instead creating a different function that creates the closure i.e.:

    我的javascript fu非常弱,但据我所知,闭包引用了堆栈上的局部变量(并且该堆栈帧与函数一起传递,同样非常粗略)。您的示例确实不起作用,因为每个函数都保留对同一变量的引用。尝试创建一个创建闭合的不同函数,即:

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

    and then:

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

    It's a bit of a kludge of course, there's probably better ways.

    当然,这有点像一块垃圾,可能有更好的方法。

    #5


    why can't you generate them all and then call something like

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

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

    EDIT:

    If you need to add more classes, just create a string in your loop with all the class names and use that as your selector.

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

    $(".li_class1, .li_class2, etc").click(function(){ this.whatever() };
    

    #6


    Or you can attach the class_id to the .data() of those list items.

    或者,您可以将class_id附加到这些列表项的.data()。

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

    Be careful, though: You're creating the callback function anew for every $("<li />") call. I'm not sure about JavaScript implementation details, but this might be memory expensive. Instead, you could do

    但要小心:您正在为每个$(“

  • ”)调用重新创建回调函数。我不确定JavaScript实现细节,但这可能是内存昂贵的。相反,你可以做到

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