javascript 閉包詳解

edithfang發表於2014-11-14
閉包:是指有權訪問另外一個函式作用域中的變數的函式。建立閉包的常見方式就是在一個函式內部建立另外一個函式。

在javascript中沒有塊級作用域,一般為了給某個函式申明一些只有該函式才能使用的區域性變數時,我們就會用到閉包,這樣我們可以很大程度上減少全域性作用域中的變數,淨化全域性作用域。
使用閉包有如上的好處,當然這樣的好處是需要付出代價的,代價就是記憶體的佔用。

如何理解上面的那句話呢?

每個函式的執行,都會建立一個與該函式相關的函式執行環境,或者說是函式執行上下文。這個執行上下文中有一個屬性 scopechain(作用域鏈指標),這個指標指向一個作用域鏈結構,作用域鏈中的指標又都指向各個作用域對應的活動物件。正常情況,一個函式在呼叫開始執行時建立這個函式執行上下文及相應的作用域鏈,在函式執行結束後釋放函式執行上下文及相應作用域鏈所佔的空間。

比如:

        
        
                
                
                        
                        
                                //宣告函式
                        

                        
                                function test(){
                        

                        
                                var str = "hello world";
                        

                        
                                console.log(str);
                        

                        
                                }
                        

                        
                                 
                        

                        
                                //呼叫函式
                        

                        
                                test();
                        

                

        


在呼叫函式的時候會在記憶體中生成如下圖的結構:



但是閉包的情況就有點特殊了,由於閉包函式可以訪問外層函式中的變數,所以外層函式在執行結束後,其作用域活動物件並不會被釋放(注意,外層函式執行結束後執行環境和對應的作用域鏈就會被銷燬),而是被閉包函式的作用域鏈所引用,直到閉包函式被銷燬後,外層函式的作用域活動物件才會被銷燬。這也正是閉包要佔用記憶體的原因。

所以使用閉包有好處,也有壞處,濫用閉包會造成記憶體的大量消耗。

使用閉包還有其他的副作用,可以說是bug,也可以說不是,相對不同的業務可能就會有不同的看法。

這個副作用是閉包函式只能取到外層函式變數的最終值。

測試程式碼如下:(這裡使用了jquery物件)

        
        
                
                
                        
                        
                                /*閉包缺陷*/
                        

                        
                                (function($){
			

			
				var result = new Array(),
			

			
				i = 0;
			

			
				for(;i<10;i++){
			

			
				result[i] = function(){
			

			
				return i;
			

			
				};
			

			
				}
			

			
				$.RES1 = result;
			

			
				})(jQuery);
			

			
				// 執行陣列中的函式
			

			
				$.RES1[0]();
			

		

	


上面的程式碼先通過匿名函式表示式開闢了一塊私有作用域,這個匿名函式就是我們上面所說的外層函式,該外層函式有一個引數$,同時還定義了變數result和 I , 通過for迴圈給陣列result賦值一個匿名函式,這個匿名函式就是閉包,他訪問了外層函式的變數I , 理論上陣列result() 會返回相應的陣列下標值,實際情況卻不如所願。

如上程式碼 $.RES1[0]() 的執行結果是10.

為什麼會這樣呢,因為i的最終值就是10.

下面我們通過下圖來詳細說明下,上面的那段程式碼執行時在記憶體中到底發生了什麼:

 
那麼這個副作用有沒有辦法可以修復呢?當然可以!

我們可以通過下面的程式碼來達到我們的預期。

        
        
                
                
                        
                        
                                /*修復閉包缺陷*/
                        

                        
                                (function($){
			

			
				var result = new Array(),
			

			
				i = 0;
			

			
				for(;i<10;i++){
			

			
				result[i] = function(num){
			

			
				return function(){
			

			
				return num;
			

			
				}
			

			
				}(i);
			

			
				}
			

			
				$.RES2 = result;
			

			
				})(jQuery);
			

			
				//呼叫閉包函式
			

			
				console.log($.RES2[0]());
			

		

	

	
		
		
			
			
				
				
					
				

				
					
				

			

		

	




上面的程式碼又在記憶體中發生了什麼?我們同樣用下面的一幅圖來詳細解釋。看懂了上面的圖,我們也就不難理解下面的圖。

 



只要看懂上面的三張圖,我們也就可以深入的理解清楚javascript中閉包的原理,以及閉包的好處和弊端,在我們的程式碼中合理的使用閉包,達到程式碼的整潔和高效。
評論(1)

相關文章