對閉包機制的深入理解


  對於JavaScript初學者來說,閉包是一個很神秘的東西,不管看多少遍,依舊搞不清楚閉包是什么,更不明白其內部是什么樣的處理機制(更可惡的是每次面試都會被問到)。

  說的含糊一點,閉包就是代碼塊和該代碼塊上下文(context)相互作用的產物。看一個例子:

function foo(){
    var x = 1;

    return function (){
        alert(++x); //2
    }
}

var bar = foo();
bar();

  先問一個問題,這里到底誰是閉包?是foo還是那個匿名函數?

 

閉包的產生原理

  在JavaScript中,函數可以用來分隔作用域,當foo執行(activation)的時候,產生了一個foo的動態作用域,然后這個動態作用域把變量x和那個return的匿名函數裝(push到棧)了進去,一般情況下,當函數執行完畢時,它會自動銷毀(pop出棧)內部產生的變量和函數,跳出這個作用域環境,返回到上一層(context)。但是在這里,由於foo作用域內部的變量和函數與它作用域外部的變量bar存在曖昧關系(bar引用了foo()的返回值),所以變量x和匿名函數沒法從foo作用域中被銷毀,於是也就產生了我們平時所說的閉包。剛才說的push到棧和pop出棧很已經顯然不適用於閉包,這和棧的結構是相悖的,那么閉包是怎樣的內存分配方式呢?這個我們后面再說。閉包既不是foo函數,也不是那個匿名函數,而是變量x、匿名函數、上下文環境三者一起同時存在的結果。

  閉包存在有這么兩個條件

  • 沒有被創建它的上下文銷毀
  • 引用了自由變量(沒有在函數塊中定義,也沒有從arguments中送入,如上匿名函數中的變量x,就是一個自由變量)

  說了這么多,再看看下面這個例子:

var x = 1;
function foo(){
    alert(x);
}

~function(){
    var x = 2;

    foo(); //1
}();

  你可能又不解了,這里怎么會彈出1呢?先說明下,下面三種寫法效果是等價的(但解析方式並不一樣,A、C是一類,B是另一類,這里就不多說了):

~function(){
    var x = 2;

    foo();    
}(); //A

(function(){
    var x = 2;

    foo();    
}()); //B

(function(){
    var x = 2;

    foo();    
})(); //C

 

閉包的內存分配方式

  回歸正題,上面為什么會彈出1,這個閉包的情況和上面所述的閉包有些不太相同,上面的閉包是因為作用域中的東西沒有被銷毀,並與上下文存在曖昧關系,而這里並不存在銷毀什么的問題,但是它依舊是一個閉包。在foo中,x是一個自由變量,當foo這個閉包產生的時候,foo的上下文會被保存,而foo處於Activation狀態的時候,它會先從他所處的Activation Object(foo內部聲明的變量、函數等非自由變量)中查找需要的對象,如果沒有找到,便會從它開始保存的上下文中查找對象,如果還沒找到,才會跑到他的上一層作用域鏈中取那個值為2的x。

  再回到之前說的那個問題,閉包的內存分配方式。很明顯,如果閉包的內存分配是利用棧的結構實現的,那進入foo運行狀態的時候,應該會push一個“全局“的x,也就是向上找到那個var x = 2,接着alert(2);但事實並非如此,上層作用域的閉包數據是動態分配的內存,也就是保存在堆里,解析器會記錄這個閉包數據被引用的次數,當引用次數為0的時候,垃圾回收機制(GC)會自動處理這些垃圾。

 

閉包是如何霸占內存的

  IE經常會因為閉包的存在而導致內存居高不下。第一個例子中:

window <=> foo <=> 匿名函數 <=> bar <=> window

  形成了一個引用循環,即便是

bar = null;

  這個匿名函數的引用次數依舊大於0。需要注意的即便是是delete一個變量並不是刪除這個變量的引用對象,而是斷開這個引用,其作用就是讓引用對象的引用次數減1. 這樣一來,這個閉包就死在內存里了,於是它也就一直占用着內存= =

 

小結

  原型鏈、閉包、作用域鏈的學習,除了對這些基本知識有一定了解之外,還需要比較多的嘗試和實踐才能理解透徹。很多次想說說閉包的含義,但是每次提筆又覺得自己沒有想明白,只好作罷。這一次對閉包的淺析,肯定也存在很多不到位或者描述錯誤的地方,如果有不同的見解,請提出來,大家相互學習!!!

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM