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