前一段时间在编写一个JS代码时遇到了这样的问题,在同一个父元素下有很多相同的li元素,我需要遍历这些元素,并给每个li元素都添加一个click时间,以显示当前li元素所在父元素的顺序。代码是这样的:
<ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> <script> var list=document.getElementsByTagName("li"); for (var i=0;i<list.length;i++){ list[i].onclick=function(){alert(i)}; }
执行结果比较有意思,当我点击不同的li的时候,每次弹窗出来都是6!
这里就有两个问题,第一个问题是为什么最终的效果不是点击哪个,弹出相应的数字?第二个问题是,弹出的数字为什么是6,而不是5或者其它的数字呢?
先说第一个问题。从实践结果可以看出,我们通过lis[i].onclick为每个对象都绑定了一个“事件处理句柄”,这里是一个没有参数的匿名函数。也就是说,我们用for循环为所有的li对象都绑定同一个处理函数,当for循环执行完毕的时候,绑定就已经完成了。当我们点击li的时候,会发生什么呢?理所当然的,相应的li上绑定的函数被调用了,上面讲到了,这个绑定的函数不带参数,函数内部需要访问一个变量,由于这个变量没有声明,也不是函数的参数,所以,从作用域的角度来讲,函数就会往上查找,在上一集作用域里,查到了i.这时候i等于多少呢?这是下一个问题,反正我们知道i的值都应该是固定的,因为for循环已经执行完毕了。理解这个问题的关键是,js中所有的对象都是以引用的方式调用,用for循环只是给所有的对象都绑定同一个事件处理函数而已,而当事件触发的时候,for循环早已经执行完毕了,i的值就已经被改变了。
第二个问题。这里主要是没有理解for循环的问题,换个方式:
var i=0; while(i<lis.length){ i++; } alert(i);
最终这里的i是登陆6,而不是通常想的,在for循环中,有个i<lis.length,所以i只能等于5,i<lis.length的意思是,只要是小于lis.length都可以执行i++,所以i的最终结果是6。
到这里,就解释了上面两个问题。
那么,怎么样去避免这两个问题的发生呢?大家百度一下,解决方法王皓桑其实是非常多的,只是很少有人讲为什么。我这里也说两种,一种比较通用的方法是利用闭包:
<script> var lis=document.getElementsByTagName("li"); for (var i=0;i<lis.length;i++){ lis[i].onclick=(function(arg){return function(){alert(arg)}})(i); } </script>
闭包可能会产生内存溢出,还有很多其它方法,大家可以自行百度,这里就不再赘述。