前一段时间在编写一段 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);
};
}
</script>
执行之后会发现一个比较有意思的结果:点击不同的 li,每次弹出的结果都是 6。
这里其实有两个问题:
第一个问题是,为什么点击不同的 li,没有弹出对应的数字?
第二个问题是,为什么弹出的是 6,而不是 5 或者其他数字?
为什么每个 li 都弹出相同的数字?
先说第一个问题。
从代码可以看出来,我们通过:
list[i].onclick =function(){
alert(i);
};给每个对象都绑定了一个事件处理函数。
这里的函数是一个没有参数的匿名函数:
function(){
alert(i);
}我们用 for 循环把这个事件处理函数绑定到了所有的 li 上。当 for 循环执行完毕以后,事件绑定也已经完成了。之后,当我们点击某一个 li 时,才会真正执行对应的事件处理函数。问题就在这里。
这个函数内部需要访问变量 i 但是 i 并不是这个函数内部声明的变量,也不是函数的参数,所以 JavaScript 会按照作用域向外查找。最终找到了 for 循环外层的 i。也就是说我们绑定给各个 li 的函数虽然分别保存到了不同的对象上,但是这些函数访问的都是外部的同一个 i。而当鼠标真正点击 li 的时候,前面的 for 循环早已经执行完了。
因此,这时候所有事件处理函数访问到的都是循环结束之后的 i。这也是为什么 点击不同的 li,得到的却是同一个结果。
为什么最终是 6?
再来看第二个问题。
为什么最后的结果是 6?
我们把 for 循环换一种方式写,就比较容易理解了:
var i=0;
while(i<list.length){
i++;
}
alert(i);
如果 list.length 是 6,那么执行过程就是:
所以最后:i的值就是:6而不是 5。
i < list.length 的意思是:只要 i 小于 list.length,循环就继续执行。
当 i 已经等于 5 的时候 5 < 6 条件仍然成立,所以还会再执行一次 i++;
这时候 i 就变成了 6 下一次判断 6 < 6 条件不成立,循环结束, 所以最终的 i 就是 6。
到这里,前面两个问题就都解释清楚了。
如何避免此类问题?
这个问题的解决方法其实有很多。
这里介绍一种比较通用的方法,就是利用闭包保存当前循环中的 i。
<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>
这里通过一个立即执行函数,把当前循环中的 i 作为参数传进去:
(function(arg){
return function(){
alert(arg);
};
})(i);每一次循环都会生成一个独立的 arg,因此最终每个 li 保存的事件处理函数,都拥有自己对应的数字。
例如:
所以点击的时候,就能够得到对应的结果。
闭包可能带来内存方面的一些问题,还有很多其他的解决方法,这里就不再展开了。
评论0
欢迎分享你的看法,也欢迎补充不同的实践经验。