for循环中的闭包问题及解决方案

时间:2021-04-28 17:36:34

说到闭包,我们首先来看一个最最简单的例子,也是最最基础的例子:为多个相同的元素,绑定事件,在点击每一个元素时,提示被点击元素的排列位置。

<span style="font-size:14px;">    <div id = "test">
<p>栏目1</p>
<p>栏目2</p>
<p>栏目3</p>
<p>栏目4</p>
</div></span>

拿到手的第一反应就是for循环添加点击事件了(添加索引值也可以!)

这里讨论闭包解决!(i=4   ,一直弹4,好烦!)

<span style="font-size:14px;">function bindClick(){
var allP = document.getElementById("test").getElementsByTagName("p"),
i=0,
len = allP.length;

for( ;i<len;i++){
allP[i].onclick = function(){ //匿名函数作为回调函数
alert("you click the "+i+" P tag!");
//you click the 4 P tag!
}
}
}
bindClick();
//运行函数,绑定点击事件</span>

这样的JS处理,看起来没有问题,可是在测试的时候,不管我们点击哪一个p标签,我们获取到的结果都是相同的,tell me why?说白了,这就是作用域到导致的一个问题。

下面来分析一下原因。首先呢,我们先把上述的JS代码给分解一下,让我们看起来更容易理解。

<span style="font-size:14px;">    function bindClick(){
var allP = document.getElementById("test").getElementsByTagName("p"),
i=0,
len = allP.length;

for( ;i<len;i++){
allP[i].onclick = AlertP;
}
function AlertP(){ //非匿名函数作为回调函数
alert("you click the "+i+" P tag!"); //发现i是未知的,沿着作用域查找i,但是i是经过for循环后得到的值,i=4
}
}
bindClick();
//运行函数,绑定点击事件</span>

这里应该没有什么问题吧,前面使用一个匿名函数作为click事件的回调函数,这里使用的一个非匿名函数,作为回调,完全相同的效果。也可以做下测试哦。

理解上面的说法了,那么就可以很简单的理解,为什么我们之前的代码,会得到一个相同的结果了。首先看一下for循环中,这里我们只是对每一个匹配的元素添加了一个click的回调函数,并且回调函数都是AlertP函数。

这里当为每一个元素添加成功click之后,i的值,就变成了匹配元素的个数,也就是i=len,而当我们触发这个事件时,也就是当我们点击相应的元素时,我们期待的是,提示出我们点击的元素是排列在第几个,这个时候,click事件触发,执行回调函数AlertP

但是当执行到这里的时候,发现alert方法中,有一个变量是未知的,并且在AlertP的局部作用域中,也没有查找到相应的变量,那么按照作用域链的查找方式,就会向父级作用域去查找,这里的父级作用域中,确实是有变量i的,而i的值,却是经过for循环之后的值,i=len。所以也就出现了我们最初看到的效果。

了解了这里的原因,那么解决方法也就很简单了,控制这个作用域的问题呗,说白了,也就一个方法,那就是在回调函数中,

用一个局部变量,来记录这个i的值,这样当再局部作用域中使用到i变量时,就会使用优先使用局部变量中的i变量的值。不会再去查找全局变量了。(定义索引值也是这个原理)


说到了这里,大概也能理解一下闭包的概念了,按照之前我们说的作用域链的说法,当一个函数运行时,该函数就会被推入作用域链的前端,当函数执行结束,这个函数就会被推出作用域链,并且销毁函数内部的局部变化和方法。

PS:闭包,说白了也就是在函数执行结束,作用域链将函数弹出之后,函数内部的一些变量或者方法,还可以通过其他的方法引用。

但是这里呢,当bindClick运行结束后,依然可以通过click事件访问到bindClick函数内部的i变量,说明bindClick函数内部的i变量,在bindClick结束后,并没有被销毁,这也就是闭包了。


重点来了,如何解决for循环中的闭包问题呢?


方法1:使得绑定click事件的目标对象和变量i都变成局部变量。这里可以直接把这两者作为形参,传递给另外的一个函数即可。(闭包中的传参)

<span style="font-size:14px;">    function bindClick(){
var allP = document.getElementById("test").getElementsByTagName("p"),
i=0,
len = allP.length;

for( ;i<len;i++){
AlertP(allP[i],i);
}

function AlertP(obj,i){
obj.onclick = function(){
alert("you click the "+i+" P tag!");
}
}
}
bindClick();</span>

这里,objiAlertP函数内部,就是局部变量了。click事件的回调函数,虽然依旧没有变量i的值,但是其父作用域AlertP的内部,却是有的,所以能正常的显示了,这里AlertP我放在了bindClick的内部,只是因为这样可以减少必要的全局函数,放到全局也不影响的。

方法2.方法1添加了一个函数进行绑定,如果我不想添加函数呢!(

自执行函数

<span style="font-size:14px;">    function bindClick(){
var allP = document.getElementById("test").getElementsByTagName("p"),
i=0,
len = allP.length;

for( ;i<len;i++){
allP[i].onclick = function (i){
return function(){
alert("you click the "+i+" P tag!");
}
}(i);
}
}
bindClick();</span>


闭包的应用

OK,这也是闭包的最简单的应用了,其他的闭包写法也有,只是就原理方面来说,和上面这种是相同的原理,所以这里就不一一列举了,用到闭包的地方其实很多(比如惰性载入函数,单例模式中的对象定义等),如果您能理解到这最简单闭包的原理,那么其他用到闭包的地方,见到了,也就能理解了。或者说,想要使用的时候,也就能想到应该怎么用了吧。