在初学JS时我们都会遇到这样一个问题:

在为一组列表元素(5个li)绑定事件时,想要获取该元素对应的索引信息。

    var lis = document.querySelectorAll("li");
    for(var i = 0; i < lis.length; i++){
        lis[i].onclick = function(){
            alert(i)
        }
    }    

//执行之后,发现每一个li点击之后弹出的都是5,并不是我们期望的0,1,2,3和4。

原因何在?

其实,我们一定要搞清楚,代码的编写和执行是分开的。换言之,事件的执行一定是分两个过程:

  • 注册过程,绑定过程

  • 触发了,执行注册好代码

注册绑定过程

在绑定的过程中,for语句一定会执行。但是函数中的alert是不会执行的

循环完毕,相当于是如下代码:

         lis[0].onclick=function(){
             alert(i)
        }
        lis[1].onclick=function(){
            alert(i)
        }
        lis[2].onclick=function(){
            alert(i)
        }
        lis[3].onclick=function(){
            alert(i)
        }
        lis[4].onclick=function(){
            alert(i)
        }

循环完毕,i的值已经是5了。注意此时绑定的事件监听函数并未执行。

触发过程

然后,当我们点击具体的某一个li时,才会真正的执行function代码

此时,i的值就是外部的i,都会弹出5

解决方法:

  • 在循环绑定事件时给每一个li元素对象增加一个索引值

  • 利用自执行函数或闭包

  1. 增加索引
    for(var i=0;i<lis.length;i++){
        lis[i].index = i;
        lis[i].onclick=function(){
            alert(this.index)
        }
    }
  1. 自执行函数和闭包
      for(var i=0;i<lis.length;i++){
          lis[i].onclick=function(n){
             return function(){
                 alert(n);
             }
          }(i)
      }

这里之所以,可以就是因为针对每一个li绑定的处理函数中,都有一个自己是局部变量,值依次是i循环的值,分别是0,1,2,3和4。

  1. let 的方式 (ES6新增)
    for(let i=0;i<lis.length;i++){
        lis[i].onclick=function(){
            alert(i)
        }
    }

尽管现在看到只有一个i,但是这个i不是全局的i,而是每一个事件处理函数自己的i。

实际上,let 的出现,其实就是为了解决这一类的问题。

总结

想清楚这个问题的关键就在于,要明白用户触发事件时,循环绑定事件这样的一个过程早已结束,所以此时事件处理程序中使用的i值,并非我们绑定时候的i值。其实也就是绑定时,事件处理程序并未执行,它的执行是在for循环绑定事件完成之后。