关于javascript:在循环中添加”click”事件侦听器

adding 'click' event listeners in loop

本问题已经有最佳答案,请猛点这里访问。

在HTML标记中为侦听器重构标准onClick,我的代码面临问题:

1
2
3
4
5
6
7
8
var td;
    for (var t=1;t<8;t++){
        td = document.getElementById('td'+t);
        if (typeof window.addEventListener==='function'){
                td.addEventListener('click',function(){
                    console.log(td);
            })}
 }

当点击td元素时,假设点击td和循环的最后一个索引,例如7。看起来,eventListeners只是为这个循环中的最后一个元素填充的。循环初始化看起来正确。为什么会这样?

这是实时代码


您需要在一个闭包中包装事件侦听器的分配,比如:

1
2
3
4
5
6
7
8
9
10
11
var td;
for (var t = 1; t < 8; t++){
    td = document.getElementById('td'+t);
    if (typeof window.addEventListener === 'function'){
        (function (_td) {
            td.addEventListener('click', function(){
                console.log(_td);
            });
        })(td);
    }
}


发生什么事了

变量td是在事件处理程序之外定义的,因此当您单击某个单元格时,会记录它设置为的最后一个值。

从技术上讲:每个事件处理程序函数都是一个闭包——一个引用外部作用域中变量的函数。

一般解决方案

此类问题的一般解决方案是从包装函数返回事件处理程序,将要"修复"的变量作为参数传递:

1
2
3
td.addEventListener('click', function(wrapTD) {
    return function() { console.log(wrapTD); }
}(td));

参数现在绑定到被调用包装函数的作用域。

更简单的解决方案:使用EDOCX1[1]

不过,还有一个更简单的选择。在事件处理程序中,this被设置为定义处理程序的元素,因此您只需使用this而不是td

1
2
3
td.addEventListener('click', function() {
    console.log(this);
});

更简单的是:没有回路!

最后,您可以完全摆脱for循环,并在整个表上设置单个事件处理程序:

1
2
3
4
5
6
7
8
var table = document.getElementById('my-table');

table.addEventListener('click', function(e) {
    if (e.target.nodeName.toUpperCase() !=="TD") return;

    var td = e.target;
    console.log(td);
});

对于较大的表来说,这是一个更好的解决方案,因为您只需要用一个事件处理程序替换多个事件处理程序。请注意,如果将文本包装在另一个元素中,则需要对此进行调整,以检查目标元素是否是td的后代。


所以这里所发生的是,您将变量"td"保持在事件侦听器函数的范围内。"td"只有一个实例,每次for循环迭代时都会更新该实例。因此,当for循环完成时,td的值现在设置为元素"td7",而事件处理程序只记录td的当前值。

在上面的示例中,您可以简单地记录"this":

1
2
3
4
5
6
7
8
9
var td;
for (var t=1;t<8;t++){
    td = document.getElementById('td'+t);
    if (typeof window.addEventListener==='function'){
      td.addEventListener('click',function(){
        console.log(this);
      });
    }
}

由于"this"设置为元素,因此为执行事件处理程序绑定了事件。

我猜您在寻找更多关于在for循环中创建闭包时保持迭代器保持不变的答案。为此,您需要在for循环之外定义一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
for (var t=1;t<8;t++){
  bind_event(t);
}

function bind_event(t) {
    var td = document.getElementById('td'+t);
    if (typeof window.addEventListener==='function'){
      td.addEventListener('click',function(){
        console.log(td);
      });
    }
}

这样,每次运行bind_事件时都会创建一个名为"td"的变量实例,并且该实例将保留在事件侦听器函数的闭包中。值得注意的是,bind_事件中的"t"也是一个新变量。


据我所知,这是因为闭包…您在for语句的范围内分配事件处理程序。当点击发生时,如果td变量在for范围内并写入日志,那么它将获取最后一个版本。


以下各项应按预期工作:

1
2
3
4
5
6
7
8
  for (var t=1;t<8;t++){
        var td;
        td = document.getElementById('td'+t);
        if (typeof window.addEventListener==='function'){
                td.addEventListener('click',function(){
                    console.log(this);
            })}
 }