什么是闭包
“高三”上解释——闭包是指有权访问另一个函数作用域中变量的函数。
通俗的将,若一个函数的执行需要用到其他的函数内变量,那么这个函数就闭包。如:
function a(){
var i = 1;
function b(){ //b就是一个闭包,只是我们无法在函数a外部调用这个闭包。
console.log(i);
}
}
闭包可以在外部环境中被调用
function a(){
var i = 1;
return function b(){
console.log(i);
}
}
var test = a();//函数a返回匿名函数,即闭包,给变量test
test();//执行闭包
闭包的作用
主要作用用两个,一是调用其它函数中的局部变量,二是将其它函数中的局部变量保存在内存中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script>
function f1(){
var n=999;
nAdd=function(){n+=1}//nAdd是一个全局变量,实现在任何时候任何地方都可以控制f1函数内部的n
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
//一共调用了两次闭包,第一次弹出999,第二次弹出1000,说明了局部变量n一直保存在内存中
</script>
</head>
<body>
</body>
</html>
小心闭包!
1.闭包有一个特性,就是只能保存外部环境变量的最后一个值。我们通过一个典型的例子来说明:
我们尝试通过for循环来为一个空数组arr赋值
var arr = [];
for(var i = 0;i < 5;i++){
arr[i] = function(){
return i;
}
}
//测试arr数组
console.log(arr[0]());//5
console.log(arr[1]());//5
console.log(arr[2]());//5
console.log(arr[3]());//5
console.log(arr[4]());//5
控制台的输出结果让我很是吃惊,尼玛弄撒呢?不是应该是0,1,2,3,4吗?咋全是5!
正如开头所说闭包只能保存外部环境变量的最后一个值,外部环境变量i循环结束过后最终值为5,所以闭包中所有的i都为5。好的,讲完了。。。呵呵,这里肯定还是会有许多顽强的小玩伴会问:为啥?
现在我们来深入理解一下上面这个函数的运行机制。上面函数每一次循环都会将function(){return i;}这个匿名函数直接赋值给每一个数组项。然后通过arr[0]()调用执行匿名函数,此时匿名函数需要访问变量i,所以匿名函数会在外部作用域中寻找i,根据闭包的定义,无疑这个匿名函数永远都是一个闭包,闭包只能保存外部环境变量的最后一个值,此时的i很明显等于5,所以arr[0]()会返回5。另外三个数值项也是这个道理。
问题的原因找到了,那怎么才能得到我们想要的0,1,2,3,4呢?每一个数组项的匿名函数function(){return i;}返回值都是通过在外部作用域中找到的,但是等匿名函数开始寻找时,外部的i早已成了一个定值5。我是否可以在外部作用域和匿名函数之间再添加一个作用域(函数),匿名函数从这个新增作用域中获取所需变量。新增函数通过立即执行的形式,从外部环境实时获取参数,相当于在新增函数作用域内定义了一个变量,这个变量就是我们的匿名函数执行时所需要的变量!
var arr = [];
for(var i = 0;i < 5;i++){
(function(m){
arr[m] = function(){
return m;
}
})(i);
}
//测试arr数组
console.log(arr[0]());//0
console.log(arr[1]());//1
console.log(arr[2]());//2
console.log(arr[3]());//3
console.log(arr[4]());//4
拿arr[0]来说,arr[0]的值为function(){return m;},现在执行它,arr[0]()需要在外部作用域中寻找变量m,果然在它的包含函数中找到了m,因为这个包含函数是一个立即执行的函数表达式(IIFA),它在for循环时就已经实时地传入了实参i并赋值给形参m,所以arr[0]()就会返回我们想要的结果。这里的IIFA有两个效果:1、封装一个函数作用域。2、立即执行函数以传递实参(如果函数不执行时不会传递参数的)。
IIFE也可以这样写:
for(var i = 0;i < 5;i++){
arr[i] = (function(m){ //arr[i]中的i具有实时性,所以arr[i]在IIFA内里面外面都行。问题的关键是我们必须将闭包包含在IIFA中,然后闭包向IIFA索取变量。
return function(){
return m;
}
})(i);
}
疑惑差不多就解决了吧。还有一个开发中常碰到的类似问题,批量注册事件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
li{
list-style: none;
float: left;
width:200px;
height:200px;
border:1px solid red;
}
</style>
</head>
<body>
<ul>
<li class="li1"></li>
<li class="li2"></li>
<li class="li3"></li>
<li class="li4"></li>
<li class="li5"></li>
</ul>
<script type="text/javascript">
//获取元素
var lis = document.getElementsByTagName("li");
//批量注册事件
for(var i = 0;i<lis.length;i++){
//iife 解决批量添加监听的时候出现的问题 尽量避免在事件函数中调用外部的变量,特别是循环变量,因为闭包。
//解决办法一
//写法一
/*(function(m){lis[m].onclick = function(){
console.log("我是老"+(m+1));
}
})(i)*/
//写法二
lis[i].onclick = (function(m){
return function(){
console.log("我是老"+(m+1));
}
})(i)
//解决办法二
//给每个li强行添加属性 让这个li记住i
// lis[i].o=i;
// lis[i].onclick = function(){
// //输出this
// alert("我是老"+(this.o+1))
// }
}
</script>
</body>
</html>
2.因为闭包可以保存其它函数的局部变量,所以闭包比普通函数更占用内存。解决办法,在闭包调用结束后将闭包调用函数赋值为null。
网友评论