// 这个代码是错误的,因为变量i从来就没被locked住
// 相反,当循环执行以后,我们在点击的时候i才获得数值
// 因为这个时候i操真正获得值
// 所以说无论点击那个连接,最终显示的都是I am link #10(如果有10个a元素的话)
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
elems[i].addEventListener('click', function (e) {
e.preventDefault();
alert('I am link #' + i);
}, 'false');
};
它为什么没有在点击第i个链接时,显示I am link #i
这是典型的 js 闭包问题。
学习这个东西最好的办法,就是设置断点用调试器debug一下,比看书和别人的文章要简单多。
事件绑定和闭包
先说明下js的事件绑定或者叫事件监听。比如下面代码:
给
a
对象绑定了click事件
,click
事件函数是弹出一个i
的值,i
在匿名函数内,没有这个变量,可是匿名函数外面的全局有这个i
,所以最后会弹出10
.这里面的过程是,堆内存定义一个匿名函数,然后栈里面有一个a
对象的变量,这个变量对象又绑定到匿名函数。例子解释
这里模仿解释器的执行过程来解释。
题主的代码运行的时候,循环一个
elems
数组,每个数组绑定一个事件函数。内存大概如下为什么不是
i
不是循环变量?很简单,解释器读取循环的代码的时候,在栈内存生成了一些变量,比如elem[0]``elem[1]
...,堆定义了一个匿名函数,这个匿名函数里面写的是什么,就是什么。比如这里匿名函数的函数内容是 alert('I am link #' + i); 每一个栈里的变量对象都绑定了堆里面那个匿名函数(事件函数).就是上面代码描述的样子。贴个简图示意一下:
然后点击对象,触发点击事件的时候,就和前面
事件绑定与闭包
的例子一样了。因为循环结束后,全局还存在一个i
的变量,并且它的值是循环之后的值,也就是10.点击的时候就执行这个匿名函数。解决方法
因为
i
相对匿名函数是外面的变量,就把循环绑定的时候,将i
的值传入到匿名函数内,就可以了。因此需要在匿名函数(事件函数)外包裹一个匿名函数, 并立即执行。如果执行点击事件的时候,最终的事件函数外层因为立即执行的匿名函数,函数体内已经存在了
num
变量,而这个num
变量是每次循环的时候传入的i
。另外一种解决方法没有用到闭包,而是给每个对象添加一个属性