闭包的问题,想必给学习js的新手们造成了成吨的暴击,尤其是下面这个问题:
<body>
<p id="p0">段落0</p>
<p id="p1">段落1</p>
<p id="p2">段落2</p>
<p id="p3">段落3</p>
<p id="p4">段落4</p>
<script type="text/javascript">
for( var i=0; i<5; i++ ) {
document.getElementById("p"+i).οnclick=function() {
alert(i); //访问了父函数的变量i, 闭包
};
};
</script>
</body>
这个代码,我是在网上找的一个典型问题,即所谓:闭包只能访问到外部函数变量的最终值。
这个结论大家都已经知道,但到底为什么,网上的说法大都比较复杂难懂。这里我找了一个比较简单的方式,来解释下。
先总结一句,即值传递和引用传递的区别。
先来看段简单的代码:
function A(){
var res=[];
for(var i=0;i<5;i++)
{
res[i]=i;
}
alert(res[0]);
}
A();
这个代码运行时, 会输出0,那么它正确的访问了下标;
然后我们稍作改动:
function A(){
var res=[];
for(var i=0;i<5;i++)
{
res[i]=function(){return i};
}
alert(res[0]);
}
A();
这个时候,一般新手会认为,输出0 或者5,其实都不对,正确答案是输出一段匿名函数的代码:function(){return i};
好,关键点来了! 为何在我们使用匿名函数后,会出来个这么东西呢? 如果有c语音基础的应该知道,那就是在传递函数时,并非值传递而是引用传递。也就是说, res[i]=function(){return i};
这段代码,会把值为匿名函数地址的指针传递给 变量res[i],而并非把i传递给res[i]。新人一般会误把变量i和代码执行划等号,其实根本是不同的东西
那这会造成什么后果?那就是res[i]变成了匿名函数的函数名了,对res[i]进行自调用的话:(res[0])();,就会出现,我们之前所讨论的问题,那就是为什么i变成了5:为什么呢?很简单,那就是每次调用匿名函数时,都会重新运行一遍匿名函数的代码。也是说会重新根据作用域链来找i的值。而循环在输出语句之前,循环结束,i自然变成了5,此时运行匿名函数的代码,找到的自然是5;
值传递和引用传递是不一样的,值传递的话,res[0]在内存中会直接存储对应的变量i的值:0,而引用传递,则会让res[0]去存储一段代码的地址即指针,每次访问res[0],其实就等同于访问匿名函数的运行结果。而循环在访问之前就已经运算结束,i的值已经变成了5,自然就会输出5了。
在让我们来看最初的代码:
document.getElementById("p"+i).οnclick=function() {
alert(i);
当触发onclick时,其实不是简单输出变量i,而是去访问匿名函数的代码的执行结果。相当于是去访问现在i的值,而不是过去那个循环中的i的值。 所以才会导致总是输出i的最终值的结果。