javascript:闭包
在阅读本篇文章之前,可以先参考我的JavaScript:作用域与作用域链及javascript预编译的过程,可以更好的理解
闭包在红宝书中的解释就是:有权访问另一个函数作用域中的变量的函数。
我们先总结一下什么是闭包:
- 什么时候需要用到闭包:
- 需要在一个函数外部,访问函数内部的变量的时候(也就是说在函数运行完之后,你想要把变量保存下来,待需要的时候调用。而不是通过垃圾回收机制消除(garbage collection))。
- 保护变量安全。一个函数的内部变量,只能内部函数引用。
- 如何定义闭包:
- 在一个函数内部,定义一个函数,并返回一个函数的引用。
在外层函数中,嵌套一个内层函数,那么这个内层函数可以向上访问到外层函数中的变量。
既然内层函数可以访问到外层函数的变量,那如果把内层函数return出来会怎样?
function outer () { var thing = '吃早餐'; function inner () { console.log(thing); } return inner; } var foo = outer(); foo(); // 吃早餐
函数执行完后,函数作用域的变量就会被垃圾回收。而这段代码看出当返回了一个访问了外部函数变量的内部函数,最后外部函数的变量得以保存。
这种当变量存在的函数已经执行结束,但扔可以再次被访问到的方式就是“闭包”。
1.闭包
1.闭包在红宝书中的解释就是:有权访问另一个函数作用域中的变量的函数。
2.写法:
1 <script type="text/javascript"> 2 function fun1(){ 3 var a = 100; 4 function fun2(){ 5 a++; 6 console.log(a); 7 } 8 return fun2; 9 } 10 11 var fun = fun1(); 12 fun() 13 fun() 14 </script>
3.效果如下:
4.分析:
执行代码
GO{
fun:underfined
fun1:function fun1()
{
var a = 100;
function fun2()
{
a++;
console.log(a);
}
return fun2;
}
}
然后第十一行开始这里,就是fun1函数执行,然后把fun1的return赋值给fun,这里比较复杂,我们分开来看,
这里fun1函数执行,产生AO{
a:100
fun2:function fun2(){
a++;
console.log(a);
}
}
此刻fun1的作用域链为 第0位 AO
第1位 GO
此刻fun2的作用域链为 第0位 fun1的AO
第1位 GO
解释一下,fun2只是声明了,并没有产生调用,所以没有产生自己的AO,
正常的,我们到第7行代码我们就结束了,但是这个时候来了一个return fun2,把fun2这个函数体抛给了全局变量fun,好了,fun1函数执行完毕,消除自己的AO,
此刻fun2的作用域链为 第0位 fun1的AO
第1位 GO
第十二行就是fun执行,然后,它本身是没有a的,但是它可以用fun1的AO,然后加,然后打印,
因为fun中的fun1的AO本来是应该在fun1销毁时,去掉,但是被抛给fun,所以现在fun1的AO没办法销毁,所以现在a变量相当于一个只能被fun访问的全局变量。
所以第十三行再调用一次fun函数,a被打印的值为102.
2.闭包之深入理解
举例1:
1 <script type="text/javascript"> 2 function fun1(){ 3 var a = 100; 4 function fun2(){ 5 a ++; 6 console.log(a); 7 } 8 9 return fun2; 10 } 11 var fn1 = fun1(); //生成自己的AO,上面有a 12 var fn2 = fun1(); 13 fn1()//101 14 fn1()//102 15 fn2()//101 16 </script>
fn1和fn2互不干涉,因为fun1函数调用了两次,所以两次的AO是不一样的。
举例2:
1 <script type="text/javascript"> 2 function fun(){ 3 var num = 0; 4 function jia(){ 5 num++; 6 console.log(num); 7 } 8 function jian(){ 9 num--; 10 console.log(num) 11 } 12 return [jia,jian]; 13 } 14 var fn = fun(); 15 var jia = fn[0]; 16 var jian = fn[1]; 17 jia()//1 18 jian()//0 19 </script>
jia和jian共用一个fun的AO,一动全都动,十二行返回了一个数组,
举例3:
1 <script type="text/javascript"> 2 function fun(){ 3 var num = 0; 4 function jia(){ 5 num++; 6 console.log(num); 7 } 8 function jian(){ 9 num--; 10 console.log(num) 11 } 12 return [jia,jian]; 13 } 14 var jia = fun()[0]; 15 var jian = fun()[1]; 16 jia()//1 17 jian()//-1 18 </script>
这里有一个坑,jia = fun()[0]; jian = fun()[1];fun函数执行了两遍,所以两次的AO不一样,所以jia和jian操作的对象不一样。
3.闭包好处与坏处
好处:
①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
③匿名自执行函数可以减少内存消耗
坏处:
①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
4.闭包解决的问题
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> <script type="text/javascript"> var lis = document.getElementsByTagName("li"); for(var i = 0;i < lis.length;i++){ lis[i].onclick = function(){ console.log(i) } } </script> </body> </html>
不管点击哪个都是10,那是因为点击事件是我们点击才触发的函数,等到触发的时候,i早就变成10跳出循环了,,这个时候我们就需要立即执行函数,创造了十个不同的作用域
解决方案:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> <script type="text/javascript"> var lis = document.getElementsByTagName("li"); for(var i = 0;i < lis.length;i++){ // lis[i].onclick = function(){ // console.log(i) // } (function(i){ lis[i].onclick = function(){ console.log(i) } })(i) } </script> </body> </html>