深刻理解JavaScript (5) —— 閉包

理解了上下文環境、做用域、做用域鏈以及自由變量,咱們再來聊聊閉包閉包

咱們不急於給出閉包的概念,先從應用閉包的兩種狀況 —— 1.函數做爲返回值,2.函數做爲參數被傳遞 —— 來直觀的認識它。函數

第一,函數做爲返回值

function fn() {
    var max = 10;
    
    return function bar(x) {
        if( x > max ) {
            console.log(x);
        }
    };
}

var f1 = fn();
f1(15);

如上代碼,bar函數做爲返回值,賦值給f1變量。執行f1(15)時,用到了fn做用域下的max變量的值。至於如何跨做用域取值,能夠參考上一節。spa

第二,函數做爲參數被傳遞

var max = 10,
    fn = function(x) {
        console.log( x > max ? true : false );
    };

(function(f) {
    var max = 100;
    f(20);
})(fn);

如上代碼中,fn函數做爲一個參數被傳遞進入另外一個函數,賦值給f參數。執行f(20)時,max變量的取值是10,而不是100,因此打印結果是true。code

上一節講到自由變量跨做用域取值時,曾經強調過:要去建立這個函數的做用域取值,而不是「父做用域」。理解了這一點,以上兩段代碼中,自由變量如何取值應該比較簡單。blog

以上是從做用域的角度對閉包的理解,咱們還能夠結合執行上下文棧來理解閉包。圖片

在前面講執行上下文棧時說到,有些狀況下,函數調用完成以後,其執行上下文環境不會接着被銷燬。這就是須要理解閉包的核心內容。ip

我們能夠拿本文的第一段代碼(稍做修改)來分析一下。作用域

clipboard.png

第一步,代碼執行前(預處理階段)生成全局上下文環境,並在執行時對其中的變量進行賦值。此時全局上下文環境是活動狀態。it

圖片描述io

第二步,執行第17行代碼時,調用fn(),產生fn()執行上下文環境,壓棧,並設置爲活動狀態。

clipboard.png

第三步,執行完第17行,fn()調用完成。按理說應該銷燬掉fn()的執行上下文環境,可是這裏不能這麼作。注意,重點來了:由於執行fn()時,返回的是一個函數。函數的特別之處在於能夠建立一個獨立的做用域。而正巧合的是,返回的這個函數體中,還有一個自由變量max要引用fn做用域下的fn()上下文環境中的max。所以,這個max不能被銷燬,銷燬了以後bar函數中的max就找不到值了。

所以,這裏的fn()上下文環境不能被銷燬,還依然存在與執行上下文棧中。

——即,執行到第18行時,全局上下文環境將變爲活動狀態,可是fn()上下文環境依然會在執行上下文棧中。另外,執行完第18行,全局上下文環境中的max被賦值爲100。以下圖:

clipboard.png

第四步,執行到第20行,執行f1(15),即執行bar(15),建立bar(15)上下文環境,並將其設置爲活動狀態。

clipboard.png

執行bar(15)時,max是自由變量,須要向建立bar函數的做用域中查找,找到了max的值爲10。這個過程在做用域鏈一節已經講過。

這裏的重點就在於,建立bar函數是在執行fn()時建立的。fn()早就執行結束了,可是fn()執行上下文環境還存在與棧中,所以bar(15)時,max能夠查找到。若是fn()上下文環境銷燬了,那麼max就找不到了。

使用閉包會增長內容開銷,如今很明顯了吧!

第五步,執行完20行就是上下文環境的銷燬過程,這裏就再也不贅述了。

相關文章
相關標籤/搜索