javascript闭包详解

1.什么是闭包?

所谓闭包:在一个函数作用域中 保留 它上级作用域的局部变量,这些局部变量不会随着上级函数的执行完毕而被销毁。

简单的理解闭包:子函数可以使用父函数的局部变量(包括参数,因为参数也是局部变量);

 

[javascript]  view plain  copy
  1. function test() {  
  2.         var i=0;  
  3.         function b() {  
  4.             alert(++i);  
  5.         }  
  6.         return b;  
  7.    
  8. }  
  9. var c= test();  
  10. c();  

闭包是和JS的垃圾回收机制(以下简写GC)相关的,正常来说在test()函数调用完毕后,为局部变量i分配的空间就会被GC回收了. 但是这里在test函数里又定义了一个子函数b,并且在函数b的里面用到了 上级函数的局部变量i,然后在test()return b.  这样一来就会导致:

test()函数调用完毕,为局部变量i分配的空间也不会被GC收回。

所以在var c= test();这句执行完毕后,调用c()依然可以访问到局部变量i,所以就弹出了1.

[javascript]  view plain  copy
  1. function parent(num1) {  
  2.         var num2= 10;  
  3.         return function child() {  
  4.             alert(num1+num2);  
  5.         }  
  6. }  
  7. var fun = parent(10);  
  8. fun();  

parent 函数内部返回值也是一个函数,在 return 的这个 child() 函数里,用到了 parent 函数的参数 num1 和局部变量 num2 因为形成了闭包,所以调用 fun() 的时候就可以访问到 num1 num2. 所以就弹出 20 .

 接着看一个稍微复杂的例子:

[javascript]  view plain  copy
  1. var name = 'zj';  
  2. var obj = {  
  3.         name:'z3',  
  4.         getName:function () {  
  5.            return function () {  
  6.                return this.name;  
  7.            }  
  8.         }  
  9. }  
  10. alert(obj.getName()());// 弹出zj  

为什么会弹出 zj 那?让我们一点点来看:

首选把alert里内容改成alert(obj.getName);看一下

 

 

 

直接alert(obj.getName)就相当于调用obj这个对象下的getName属性,弹出getName这个属性的值,所以弹出是一大段代码接着改写成alert(obj.getName( ) ),看下结果

 

直接alert(obj.getName( ) )就相当于直接执下面的代码:

[javascript]  view plain  copy
  1. function () {  
  2.         return function () {  
  3.                return this.name;  
  4.     }  
  5. }  

而执行这个函数执行之后返回值又是一个函数,所以alert(obj.getName( ) )还是一段函数代码

[javascript]  view plain  copy
  1. function () {return this.name;}  
接着把alert(obj.getName( ) )改成alert(obj.getName( ) ( )) , 到这里就相当于直接运行

function () { return this.name; }这段代码,此时的this代表的就是window这个对象,所以return this.name 就相当于return window.name ,所以就弹出zj了,到这里有没有看明白

其实alert(obj.getName( ) ( ))这种写法和前面的用一个变量来接受的写法是等价的,同样我们也可以用个变量来接受

[javascript]  view plain  copy
  1. var n = obj.getName();  
  2. alert(n());  

这样弹出的结果也是 zj

 2. 怎么样才能形成闭包?

在有函数嵌套的情况下,子函数用到了父函数的参数或者局部变量,一般都会形成闭包。

3. 闭包有什么好处?

好处1:具有封闭性,可以起到保护变量的作用

[javascript]  view plain  copy
  1. // 1级作用域  
  2. function f(x){ //2级作用域  
  3. var temp = x;  
  4. return function(x){//3级作用域  
  5.    temp += x;  
  6.    alert(temp);   
  7. }  
  8. }  
  9. var a = f(50);  
  10. a(5); // 55  
  11. a(10);  

我们在二级作用域里定义了一个局部变量 temp ,然后再三级作用域里用到了这个变 量,这里就形成了闭包,把 temp 这个变量 保护起来,不被 GC 所回收,方便在以后再 使用 temp 这个变量。

当执行完这句话 var a = f(50);temp=50并且temp变量的值被保存了起来,接着调a(5)此时temp的值就变成了55,所以会弹55。a(5)之后,temp里保存的值是55.a(10).所以弹出65。 

好处2 :避免全局变量的污染

[javascript]  view plain  copy
  1. var a = 1;  
  2. function test(){  
  3. a++;  
  4. alert(a);  
  5. }  
  6. test(); // 调用一次a就加1  
  7. test();  
此时的 a 是全局变量,程序当中经可能的避免全局变量,此时把 a 提到内部,变成局部变量看一下

[javascript]  view plain  copy
  1. function test(){  
  2.     var a = 1;  
  3.     a++;  
  4.     alert(a);  
  5. }  
  6. test(); //2  
  7. test();//2  

我们发现不管调多少次结果都是 2. 这也是因为每调用一次 test(); 都会重新定义变量 a = 1 ,然后累加一次变成 2 ,然后弹出就结束了;再调用 test()  还是重新定义变量 a = 1..., 所以永远是弹出 2;

这和js的GC有关,当函数test()执行完毕后,GC就会把为test的局部变量分配的空间收回,所以局部变量就不存在了;因为每次调用test()都是重新为局部变量分配空间;

这里就可以通过闭包就可以实现:test()调用完毕后不让GC把局部变量的空间收回,同时又避免了全局变量污染

 

[javascript]  view plain  copy
  1. function test(){  
  2.             var a = 1;  
  3.             function b(){  
  4.                 a++;  
  5.                 alert(a);  
  6.             }  
  7.             return b;  
  8. }  
  9. var c = test(); //2  
  10. c();//2  
  11. c();  
  12. c();  

再来看一个例子:

[html]  view plain  copy
  1. <ul>  
  2.     <li>000li>  
  3.     <li>111li>  
  4.     <li>222li>  
  5.     <li>333li>      
  6. ul>  

4 li ,当点击第一个 li 弹出 0 ,点击第二个弹出 1 ,点第三个弹出 2 ,点第四个弹出 3

[javascript]  view plain  copy
  1.   

这样写完之后发现每次点击弹出来都是 4, 因为当每次当我们点击 li 的时候,其实 for 循环已经循环结束了,所以每次都会弹出 4

这里就可以利用闭包在aLi[i].onclick = function () { alert(i);} 外面再套一个自执行函数,然后把for循环里的局部变量i当作参数传给这个函数,这样每次循环就会把i的值保存起来,这样外部函数执行完毕之后,并不会影响内部函数。

[javascript]  view plain  copy
  1.   
所以这样每次点击第一个 li 就会弹出 0 ,点击第二个弹出 1 ,点第三个弹出 2 ,点第四个弹出 3

4. 闭包会引起哪些问题?

闭包在IE下引发内存泄露

所谓内存泄露是指: 我们定义了一个变量,而这个变量会常驻在内存中不被释放,只有当关闭浏览器的时候才会被释放;

 1. 什么条件下可能导致内存泄露?

当一个变量的属性,引用一个内部函数,而在这个函数内部 又 用到了函数外部的 变量的其他属性或者引用的时候,就会导致内存泄露

例如:

[javascript]  view plain  copy
  1. window.onload = function(){  
  2.     var oDiv = document.getElementById('div1');  
  3.     oDiv.onclick = function(){  
  4.         alert(oDiv.id);  
  5.     }  
  6. }  

var oDiv 这个变量的  onclick 属性引用了一个匿名函数,而在这个函数内部又用到了 oDiv.id  的引用,因为 oDiv 是在这个匿名函数外部定义的,此时就会引起内存泄露;

2. 那怎么解决内存泄露问题那??

[javascript]  view plain  copy
  1. window.onload = function(){  
  2.     var oDiv = document.getElementById('div1');  
  3.     oDiv.onclick = function(){  
  4.         alert(oDiv.id);  
  5.     }  
  6.     window.onunload = function(){  
  7.         oDiv.onclick = null;  
  8.     }当页面跳转的时候把oDiv.onclick引用置为空就好了  
  9. }  

或者是

[javascript]  view plain  copy
  1. window.onload = function(){  
  2.     var oDiv = document.getElementById('div1');  
  3.     var id = oDiv.id;  
  4.     oDiv.onclick = function(){  
  5.         alert(id);  
  6.     }  
  7.     oDiv = null;  
  8. }  

在内部匿名函数引用之前把 oDiv.id  通过一个变量接受一下,然后用完之后把 oDiv 置为空也可以解决内存泄露问题。

你可能感兴趣的:(javascript闭包详解)