文章出自http://www.cnblogs.com/snandy/archive/2011/03/01/1967628.html
有个网友问了个问题,如下的html,为什么点击所有的段落p输出都是5,而不是alert出对应的0,1,2,3,4。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <!DOCTYPE HTML> <html> <head> <meta charset= "utf-8" /> <title>闭包演示</title> <style type= "text/css" > p {background:gold;} </style> <script type= "text/javascript" > function init() { var pAry = document.getElementsByTagName( "p" ); for ( var i=0; i<pAry.length; i++ ) { pAry[i].onclick = function () { alert(i); } } } </script> </head> <body onload= "init();" > <p>产品 0</p> <p>产品 1</p> <p>产品 2</p> <p>产品 3</p> <p>产品 4</p> </body> </html> |
以上场景是初学者经常碰到的。即获取HTML元素集合,循环给元素添加事件。在事件响应函数中(event handler)获取对应的索引。但每次获取的都是最后一次循环的索引。
原因是初学者并未理解JavaScript的闭包特性。通过element.onclick=function(){alert(i);}方式给元素添加点击事件。响应函数function(){alert(i);}中的 i 并非每次循环时对应的 i(如0,1,2,3,4)而是循环后最后 i 的值5。 或者说循环时响应函数内并未能保存对应的值 i,而是最后一次i++的值5。
了解了原因,摸索出了很多解决办法(纯粹是兴趣)。最先想到的前两种
1、将变量 i 保存给在每个段落对象(p)上
1 2 3 4 5 6 7 8 9 | function init1() { var pAry = document.getElementsByTagName( "p" ); for ( var i=0; i<pAry.length; i++ ) { pAry[i].i = i; pAry[i].onclick = function () { alert( this .i); } } } |
2、将变量 i 保存在匿名函数自身
1 2 3 4 5 6 7 8 | function init2() { var pAry = document.getElementsByTagName( "p" ); for ( var i=0; i<pAry.length; i++ ) { (pAry[i].onclick = function () { alert(arguments.callee.i); }).i = i; } } |
后又想到了三种
3、加一层闭包,i 以函数参数形式传递给内层函数
1 2 3 4 5 6 7 8 9 10 | function init3() { var pAry = document.getElementsByTagName( "p" ); for ( var i=0; i<pAry.length; i++ ) { ( function (arg){ pAry[i].onclick = function () { alert(arg); }; })(i); //调用时参数 } } |
4、加一层闭包,i 以局部变量形式传递给内层函数
1 2 3 4 5 6 7 8 9 10 11 | function init4() { var pAry = document.getElementsByTagName( "p" ); for ( var i=0; i<pAry.length; i++ ) { ( function () { var temp = i; //调用时局部变量 pAry[i].onclick = function () { alert(temp); } })(); } } |
5、加一层闭包,返回一个函数作为响应事件(注意与3的细微区别)
1 2 3 4 5 6 7 8 9 10 | function init5() { var pAry = document.getElementsByTagName( "p" ); for ( var i=0; i<pAry.length; i++ ) { pAry[i].onclick = function (arg) { return function () { //返回一个函数 alert(arg); } }(i); } } |
后又发现了两种
6、用Function实现,实际上每产生一个函数实例就会产生一个闭包
1 2 3 4 5 6 | function init6() { var pAry = document.getElementsByTagName( "p" ); for ( var i=0; i<pAry.length; i++ ) { pAry[i].onclick = new Function( "alert(" + i + ");" ); //new一次就产生一个函数实例 } } |
7、用Function实现,注意与6的区别
1 2 3 4 5 6 | function init7() { var pAry = document.getElementsByTagName( "p" ); for ( var i=0; i<pAry.length; i++ ) { pAry[i].onclick = Function( 'alert(' +i+ ')' ); } } |