DOM的addEventListener函数一个陷阱。

时间:2015年7月9日

这个函数是一个DOM Level3级方法,注册事件的,用法是:

DOM对象.addEventListener("事件名称(比如click)",事件执行的函数(可以是匿名函数或者函数名),false/true(false表示冒泡方式,true表示捕获方式));

这里注意第二个参数好像不是立即就编译进去(当然JS是没有编译的东西的,只是为了表述),而是在事件触发的时候才运行里边的代码,包括变量赋值。

比如下边代码

<a href="###">连接一</a>
<a href="###">连接二</a>
<a href="###">连接三</a>
<a href="###">连接四</a>
<a href="###">连接五</a>
<a href="###">连接六</a>
<a href="###">连接七</a>
<a href="###">连接八</a>
<a href="###">连接九</a>

<script>
var myHref = document.getElementsByTagName("a");
for (var i = 0,mylength = myHref.length; i<mylength; i++) {
    myHref[i].addEventListener("click",function(e){
        e.preventDefault();
        alert(i);
    },"false");
}
</script>

我们希望的结果是点击每一个a会弹出对应的数组标号值,也就是第一个a点击后显示0,第二个显示1。但是实际上我们发现并不是这个结果,而是总是显示最后一个a标签对象的数组标号值+1,也就是9。

所以我认为,函数并不是立即执行,而只是发函数的引用放在那了,当事件发生的时候,才去调用实际的函数体,而且i做为全局变量,一直保存了下来,如果我们最后对i进行了别的操作,比如这样:

<script>
var myHref = document.getElementsByTagName("a");
for (var i = 0,mylength = myHref.length; i<mylength; i++) {
    myHref[i].addEventListener("click",function(e){
        e.preventDefault();
        alert(i);
    },"false");
}
i=555;
</script>

每一个连接弹出的就是555了。实际i是做为全局变量的(js没有块作用域,所以for循环内的i不是局部变量,而是全局变量),我又想,如果我把i让内存回收掉,是不是就会报错了,结果我试了一下并不能回收,可能是因为闭包的关系。其实浏览器里即使是全局函数都是一个闭包,具体为什么我这里理解,大家可以找一找闭包的相关资料看一下。

那么如何实现我们想要的效果,还是得用到闭包啊,我们需要把当时的i保存下来。代码如下:

<script>
var myHref = document.getElementsByTagName("a");
for (var i = 0,mylength = myHref.length; i<mylength; i++) {
    (function(i){  //这里的i跟外部的i实际不是一个i
        myHref[i].addEventListener("click",function(e){
            e.preventDefault();
            alert(i);
        },"false");
    })(i);
}
i=555;    //不会影响
</script>

这样写,实际上for循环里是一个立即执行的函数表达式,这种写法()();是立即执行了,立即执行我们的闭包,我们把i当参数传给闭包,闭包里边的i实际作用域只在闭包里,跟外部的i不是一个i,因此就能实现我们想要的效果了。

总结的不好,见谅。

-----2015年7月10日,我今天又脑洞大开了一下,为什么会出现这个陷阱。因为click事件(或者所有的DOM事件?反正setTimeout和setInterval也是回调机制处理)是回调机制处理的,由于javaScript是单线程的语言,它会按照javaScript语法出现的次序顺序执行(声明提升实际在这之前发生的),但是遇到click等事件的时候,并不是立刻就执行click对应的函数了了,只是把click事件的函数引用放在一个队列里,继续执行下边的代码,等所有代码执行完毕,就查看任务队列里找第一个,就执行前边存放的click事件的引用,而那时候才进行了所有的赋值,判断等操作,也就是i无法保留的原因。

按照上边有问题的代码来解释:

javascript会先执行getElementsByTagName,然后执行for,for里边有一个addEventListener,这个函数也会执行,但是注意,addEventLister的第二个参数,并不是立刻执行了,只是发函数引用放在那了,然后一直到for循环,再然后执行i=555。

注意:javascript除了主线程,还有一个任务队列的东西,主线程执行完毕了,就去队列找任务,当然我们不点击的话,任务队列就是空的,当我们点击了,addEventLister就会把他的第二个参数的函数放到队列里,然后javaScript主线程突然发现队列里有东西了,赶紧拿出来用吧,但是那时候,大妈已经不是以前的大妈了。。。。。。。

所以,其实也不是陷阱,是javaScript机制导致的问题。

阮大侠的博客有介绍,event loop机制的问题。

原文地址:https://www.cnblogs.com/jingubang/p/4632506.html