代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
<title>Document</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
</head>
<body>
<div>
<ul>
<li>春</li>
<li>夏</li>
<li>秋</li>
<li>冬</li>
</ul>
<script type="text/javascript">
var list = document.getElementsByTagName('li');
var len = list.length;
for (var i = 0; i < len; i++) {
list[i].onclick = function () {
alert(i);
}
}
</script>
</div>
</body>
</html>
想要的效果,分别点击春夏秋冬,弹出0,1,2,3,为什么实际结果都是弹出4。应该如何修改?
这个是典型的闭包应用了,正确的写法如下:
var list = document.getElementsByTagName('li');
var len = list.length;
var i = 0;
for (i; i < len; i++) {
(function (n) {
list[n].onclick = function () {
alert(n);
};
})(i)
}
你的写法之所以不会得到想要的结果原因如下:
list[i].onclick = function () { alert(i) };
是一个赋值表达式,Javascript 解析到这里的时候,会把等号右边的匿名函数定义赋值给左边的onclick
事件,其含义可解读为:“当点击事件发生的时候,执行回调函数内部的内容“然而,Javascript 在解析到这些地方(共四次)的时候并不会直接执行匿名函数内部的内容,否则得话用不着你点击了代码一载入就会立刻
alert
四次,对吧?实际上,四个alert(i)
被注册给onclick
事件,它们没有被立刻执行,而是等待着onclick
事件的触发——这一点实际上是异步的特性(如果是同步的话,代码一载入就应该执行了)于是,当你真的开始点击元素的时候,此时由于 for 循环已经解析完毕,回调函数执行内部内容的时候
i
已经是3
了,这个值被存在内存当中,每一次回调函数运行都是去读取那个值,你自然会看到四个而不是3
0, 1, 2, 3
这里纠正一下,不是四个3
而是四个4
,因为循环最后还执行一次i++
那么我们就知道了,想要达到预期的效果,其关键就是在每次循环的时候都需要把当前的循环变量单独记住,而不是去读取循环变量的最终值。这就是闭包发挥其作用的经典场景了。
闭包的使用在每一次循环的时候都创建了独立的函数作用域,并且在内存中保留那一刻的执行上下文(我们通过把每次的循环变量传递给 IIFE 实现这一点,IIFE 就是用来创建闭包的手段)。于是这一次在你点击的时候,回调函数里 alert(n)
寻找到的值就变成了当时循环变量的值。正确的结果就是这样得到的。
使用闭包的关键就在于正确的理解函数作用域以及对代码的解析和执行过程了然于心。闭包的主要特性就在于它能够保存在其定义生效时周围的执行上下文,而无论此后这些上下文是否改变。因此,如果你无法准确的分析出执行上下文变化的情况,就很难利用闭包的这一特性了。