javascript執行上下文、作用域與閉包(第六篇)---閉包

終於講到閉包了,當你在百度上搜索閉包時,你會被搜索出來的結果嚇一跳,我的天,爲什麼說得都不一樣?直到把所有的解釋都看過了,我就只想說一句,到底誰說的是對的…

在這麼多的不同解釋裏,我認真思考了很久,到底該相信誰?最後我選擇相信大道至簡,因爲我始終覺得理論來源於實踐,而實踐一定不是在象牙塔裏,而是可以摸得到的簡單的東西。

下面就來講最原始的閉包的示例:

function fn(){
var max=10;
return function bar(x){
if(x>max)
{
alert(x);
}
}
}
var f1=fn();
f1(15);//15

如上代碼,bar函數作爲返回值,賦值給f1變量。執行f1(15)時,就相當於執行bar(15),沿着作用域鏈取到fn作用域下的max變量的值。

這已經產生了一個最基本的閉包,用自然語言描述的話就是:

(1)定義普通函數 A

(2)在 A 中定義普通函數 B

(3)在 A 中返回 B

(4)執行 A, 並把 A 的返回結果賦值給變量 C

(5)執行 C

而它的形式就是用“return”作爲橋樑,鏈接A外的變量C和A內的變量B。

用一個歸納的話說就是:

當一個內部函數被其外部函數之外的變量引用時,就形成了一個閉包

如果你認爲這就是閉包的全部,那你就有些狹隘了。

下面一個例子,就展示了這個狹隘之處:

function A(){
    var count=0;
    function B(){
        count++;
        alert(count);
    }
    return B;

}
 var c=A();
 c();//1
 c();//2
 c();//3

那麼你就會驚奇於閉包還有如此神祕的一面,其實對於這一面,網上有很多解釋,解釋得五花八門,但大都沒解釋到本質上。

其實這一系列的文章的標題都是“javascript執行上下文、作用域與閉包”,說明閉包與執行上下文,作用域是有緊密聯繫的,下面就來演示一下閉包那神祕的一面的本質。

咱們可以拿本文的第一段代碼(稍作修改)來分析一下。

function fn(){
var max=10;
return function bar(x){
if(x>max)
{
alert(x);
}
}
}
var f1=fn(),
max=100;
f1(15);//15

我們來一步一步揭開那神祕的面紗:

在這之前,告訴大家一個很重要的東西—-我們在前面講執行上下文棧時當一個函數被調用完成之後,其執行上下文將被被彈出,即銷燬,其中的變量也會被同時銷燬。

但我們要銘記:在閉包裏,函數調用完成之後,其執行上下文環境不會立即被銷燬。(這句話是不正確的,但這裏讀者可以先用這個錯誤的解釋去看下面的步驟,先對閉包形成的原因有一個大致的瞭解,最終正確的理解在 厚積薄發—從此再也不用擔心閉包問題

第一步,代碼執行前生成全局上下文環境,並在執行時對其中的變量進行賦值。此時全局上下文環境是活動狀態:
這裏寫圖片描述

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

第三步,執行完第17行,fn()調用完成。按理說應該銷燬掉fn()的執行上下文環境,但是因爲執行fn()時,返回的是一個函數。函數的特別之處在於可以創建一個獨立的執行上下文,當代碼執行到return時,按理說應該把fn()的執行上下文銷燬,但是由於return的bar()存在對fn()上下文的引用,(因爲存在對變量max的需要)所以不能將其銷燬,這裏就要提到js的垃圾回收機制,當一個函數不存在外部對它的引用的時候,就自動將其銷燬。這裏存在對把不能把fn()上下文銷燬。

第四步,執行到第18行時,全局上下文環境將變爲活動狀態,但是fn()上下文環境依然會在執行上下文棧中。另外,執行完第18行,全局上下文環境中的max被賦值爲100。如下圖:
這裏寫圖片描述

第五步,執行到第20行,執行f1(15),即執行bar(15),創建bar(15)上下文環境,並將其設置爲活動狀態
這裏寫圖片描述

執行bar(15)時,max是自由變量,需要向創建bar函數的作用域中查找,找到了max的值爲10,

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

可以看到,使用閉包會使變量保存在內存中,但是缺點就是會增加內存開銷。

在網上有些資料解釋閉包的主要用途有兩個,一個是可以使外部變量訪問到一個函數的內部變量,一個是使變量保存在內存中。其實這句話不太貼切,正確的說法應該是:使外部變量訪問到一個函數的內部變量是閉包的形式,使變量保存在內存中是閉包的工作原理。

到這裏,如果覺得有種恍然大悟的感覺,不妨試一試下面的例子

function A(){
    var count=0;
    function B(){
        count++;
        alert(count);
    }
    return B;

}
 var c=A();
 c();//1
 c();//2
 c();//3

大家可以試一試解釋爲什麼三次調用c(),結果分別是1,2,3呢?

如果還有些不懂,我會在下一篇裏詳細講一下我的理解。


本文參考了王福朋老師的深入理解javascript原型和閉包(15)——閉包

發佈了32 篇原創文章 · 獲贊 23 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章