1. 程式人生 > >從JS執行過程分析閉包的概念

從JS執行過程分析閉包的概念

(自己理解,有誤請指正 )

首先,閉包這個詞真讓人難理解,中華文化博大精深,講究見名知意,但是閉包確實是理解一門語言特性中不可或缺的一個東西,網路上資料千奇百怪,講了一通也很難讓人有醍醐灌頂的感覺,這裡我來說一說我對閉包的理解:

# 閉包是和特定語言無關的東西

# 閉包是指的是通過特定方式允許跨作用域訪問變數

# 先分析下常規的變數作用域:

首先,人類程式語言基本分為兩種,一種是函式式的程式語言,一種是面向物件的程式語言;

對於函式式語言,以函式為執行單位,一個變數宣告在一個函式裡,只能作為區域性變數,變數作用域只在本函式體內部,另外一個函式不允許訪問本函式內部的區域性變數,例如:

void a(){ int i = 1; }

void b(){ println(a.i); }

b函式是不允許訪問a函式中的變數的,實際上,在函式最終執行過程中,每執行一個函式就會有一個棧幀壓入堆疊,函式中的區域性變數是放在本函式的私有棧幀中的,執行另一個函式的時候會壓入一個新的棧幀,每當函式執行完畢後棧幀就會消除,不支援閉包的語言體現在不允許跨棧訪問變數。

對於面向物件的語言,不允許跨作用域訪問體現在一個類物件不能訪問另一個類物件的私有成員變數。

# 支援閉包的語言的變數作用域是啥樣的呢?

函數語言程式設計語言舉一個JS閉包的例子

function outer() {
     var  a = '變數1'
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner 就是一個閉包函式,因為他能夠訪問到outer函式的作用域
}

外部呼叫var bb = outer(); bb();這樣就能在控制檯打印出outer函式作用域內部的變數的a的值。

面向物件的程式語言的閉包的例子就是java的內部類,體現在外部呼叫一個類的內部類物件的方法能夠訪問外部類物件的私有成員變數。

 # 從JS的執行過程來分析JS的閉包是如何實現的

JS是個解釋型語言,解釋引擎在載入了一個JS原始檔後,首先進行詞法分析,分詞後得token資訊,然後構建抽象語法樹,然後開始根據抽象語法樹開始解釋執行程式碼。js一個函式解釋執行過程中會對應一個執行上下文,每呼叫一個函式就會生成一個執行上下文並壓入執行上下文堆疊,函式執行上下文中有一個活動物件,函式的引數、函式內部定義的變數、函式內部定義的巢狀函式等都會作為屬性掛載到這個活動物件上。分析下面的例子:

function a(){
    var i = 0;
    function b(){
        alert(i);
    }
    return b;
}

var fun = a();
fun();

 

直譯器執行到函式a時,在記憶體中為函式a分配執行上下文,並壓入執行上下文堆疊,執行上下文中包含當前函式的活動物件;

分別將a函式體內宣告的變數i和函式b作為屬性儲存到當前上下文的活動物件中;

執行函式返回操作,把函式b的引用返回給外層函式定義的變數fun,然後銷燬當前函式的執行上下文,但是由於外層持有了本函式的活動物件中的變數,本函式執行上下文中的活動變數並不會被銷燬;

執行fun函式,此時的fun就是a的內部函式b的一個引用,直譯器為該函式分配新的執行上下文併入棧,這裡注意:由於函式b是定義在函式a中的活動物件中的,此時在函式b的執行上下文中的活動物件中會包含一個指向a函式的活動物件的引用,這樣兩個函式的活動物件就串了起來,組成一個變數作用域鏈。直譯器在執行函式b的alert(i)語句時,會先在b函式自己的活動物件中找有沒有變數i的定義,如果沒有的化就會沿著作用域鏈查詢,從上層的a函式的活動物件中找到了變數i的定義,於是輸出了i的值;

通過上面描述的方式,達到了外層函式訪問函式a中定義的私有變數i的目標--這就是所謂的閉包。