<ul>
<li class="id1">这是一个例子</li>
<li class="id2">这是一个例子</li>
<li class="id3">这是一个例子</li>
</ul>
以上的代码,我们要实现的一个功能是,实现点击每个 li
标签时候弹出当前点击的那个 li
标签的索引,这里我们用的是 javascript
的原生方法。
刚开始的时候,我自以为是的用了下面的方法,相信在没有了解闭包之前的朋友们应该都有这么写过
var len = document.querySelectorAll('li').length; //获取li标签的长度
var obj = document.querySelectorAll('li'); //取得li这个对象
for(var i = 0;i < len ; i++){
obj[i].onclick=function(){
alert(i);
};
}
错误理解,循环三次,依次对 li
标签绑定点击事件,在绑定的时候弹出循环的索引(此时循环的索引是和 li
标签的索引是一致的)。
现在当我们去点击的时候,会发现无论我们点击那个标签,弹出的都是3,显然这不是我们任何一个表现的索引,但是我们发现我们标签的长度是3,最大的索引是2,而这里的3正好是 for
跳出循环的值。为了验证这个说法,我们逐步次添加标签,再次证明了我们的说法。
为什么我们在点击的时候,取不到正确的 i
值?
给for
循环打断点,我们发现i
确实经历了从0到2的步骤,并且给obj[0] obj[1] obj[2]
都依次绑定了点击事件,到i
到3的时候,不满足循环条件,所以跳出。这里就引入了我们的另外一个概念,同步和异步。
点击事件是一个异步事件,初始化的时候,我们绑定了点击事件,但是并没有触发点击事件的发生。因为是点击时异步执行的,javascript是单线程,需要把for
循环执行完毕,当我们触发点击事件的时候,此时i
值已经到达了3,之前的i
并没有被保存下来,跳出了判断。我们要做的就是,在绑定事件的时候,也把绑定时的值传到事件内部。
在一个函数内部,添加一个函数,内部函数可以访问外部函数的变量(相反,外部函数不可以访问内部函数),这就形成了一个闭包。
var len = document.querySelectorAll('li').length;
var obj = document.querySelectorAll('li');
console.log(document.querySelectorAll('li').length);
for(var i = 0;i < len ; i++){
(function(i){
obj[i].onclick=function(){
alert(i);
};
})(i);
}
这里通过自执行的匿名函数,就实现了每次循环的时候,把i
值传到事件内部。循环给obj
绑定事件的时候,就把i
给保存了下来。
闭包的深入理解
游览器是实现时就认为只要存在访问上层作用域的可能性,就会被当成是一个闭包。
总结一下闭包:
- 闭包是在函数被调用执行的时候才被确认创建的。
- 闭包的形成,与作用域链的访问顺序有直接关系。
- 只用内部函数访问了上层作用域链中的变量对象时,才会形成闭包,因此我们可以利用闭包来访问函数内部的变量。
Array.proyotype.map
这个方法可以很方便的解决上面的问题,而不用用到闭包的概念
var arr = [obj[0],obj[1],obj[2]];
arr.map(function(currentValue,index,array){
currentValue.onclick=function(){
alert(index);
}
});
因为这里这个方法的其中一个参数就是索引,合理的避开了上面的问题。当然这里的前提是要先把每个对象转成数组。因为这是在Array
对象原型上的方法。
var len = document.querySelectorAll('li').length;
var obj = document.querySelectorAll('li');
console.log(document.querySelectorAll('li').length);
var arr = [];
for(var i = 0;i < len ; i ++){
arr.push(obj[i]);
}
arr.map(function(currentValue,index,array){
currentValue.onclick=function(){
alert(index);
}
});