簡單而清楚地理解閉包

什麼是閉包?
「閉包是指有權訪問另外一個函數做用域中的變量的函數。」---《JavaScript高級程序設計》
一般來講,當一個函數能夠訪問另外一個函數內部定義的變量(包括屬性和方法)時,這個函數能夠稱之爲閉包:閉包

function fnA(){
    var a = "this is fnA.a";
    return function fnB(){
        alert(a);
    }
}

var x = fnA();
x(); // "this is fnA.a"

例子中,咱們能夠經過x(即fnB)去訪問fnA中的內部變量(a),此時咱們能夠稱fnB爲閉包。函數

閉包是如何產生的?
爲了更清楚的解釋閉包的發生,咱們須要先明白「函數的建立」到「函數的調用」到底發生了什麼事情。this

一、函數被建立時,會建立一條做用域鏈(下稱A鏈)。而後根據跟建立時的環境,依照「外部函數」、「‘外部函數’的外部函數」、「‘外部函數的外部函數’的外部函數」....「全局函數」順序,將全部函數的活動對象(能夠簡單理解爲全部的內部變量)添加到這條做用域鏈上。(大多數非閉包的狀況下,函數的外部函數即全局變量)
二、函數被調用時,也會建立一條做用域鏈(下稱B鏈),並將A鏈的內容包含到B鏈中,而後將當前函數的活動對象(能夠簡單理解爲全部的內部變量)添加到B鏈條的頂端。
三、當訪問函數內部變量時,會按照B鏈中的變量保存的順序依次訪問。即內部變量,(建立時的)外部函數的變量,(建立時的)外部函數的外部函數的變量...全局變量。設計

下面是一道經典的閉包題:code

function fun(n,o) {
    console.log(o)
    return {
    fun:function(m){
        return fun(m,n);
        }
    };
}

var a = fun(0); // undefined。因爲會「o」未賦值,因此會顯示:undefined。同時返回一個字面量對象,對象內建立一個名爲「fun」的函數,並將對象返回賦值給全局變量a。此時a內部的函數fun已經被建立好了,它的做用域鏈上包含了外部函數(外層的fun函數)的全部變量,其中包含了n(值爲0),o(值爲undefined);以及全局函數的變量fun(值得注意的是,這個fun屬於全局函數的變量)。
a.fun(1); // 0。上面提到。在建立a的內部fun時,它包含的做用域鏈中包含了n(值爲0),o(值爲undefined);以及全局函數的變量fun。所以,咱們調用(訪問)的「fun」是做用域鏈中給全局函數的函數fun。m=1,n=0,將其賦值給全局函數的函數fun,即:n=(m=)1,o=(n=)0,打印0,值爲「0」。
a.fun(2); // 0
a.fun(3); // 0。這裏有個「坑」須要注意。在上個步驟「a.fun(1);」中,最後會建立一個對象(fun函數做用域鏈中的n值爲1,o值爲0)並返回。可是並無變量來接收這個對象,更不會影響到a內部做用域鏈。所以「a.fun(2);」、「a.fun(3);」中,做用域鏈上的值與「a.fun(1);」中徹底同樣。


var b = fun(0).fun(1).fun(2).fun(3); // undefined,0,1,2
//這是一條鏈式調用。爲了便於理解,咱們將鏈式調用拆分如下等價的方案:
var b1 = fun(0); // undefined。這個和「 var a = fun(0);」,不重複解釋。
var b2 = b1.fun(1); // 0。這裏和「a.fun(1);」同樣,不重複解釋。可是要注意的是,此時有個變量b2接收了b1.fun返回的變量。此時,b2中的函數fun的做用域鏈的(部分)內容狀況:n=1,o=0。
var b3 = b2.fun(2); // 1。「var b2 = b1.fun(1);」中,b2中函數fun的做用域鏈中的n爲1,o爲0。調用全局函數的fun時,n=(m=)2,o=(n=)1。所以打印內容爲「1」。
var b4 = b3.fun(3); // 2。理由同上。


var c = fun(0).fun(1); // undefined,0
c.fun(2);// 1
c.fun(3);// 1

//爲了便於理解,咱們將鏈式調用拆分如下等價的方案進行解釋:
var c1 = fun(0); // undefined。這個和「 var a = fun(0);」,不重複解釋。
var c = c1.fun(1); // 0。要注意的是,「 c1.fun(1); 」返回的對象由變量c接收,即c中的函數fun做用域鏈中的變量:n=1,o=0。
c.fun(2);// 1。
c.fun(3);// 1。「 c.fun(2);」中返回的對象不會影響到c。所以此處和執行「c.fun(2);」時同樣,c中的函數fun做用域鏈並未被改變。

咱們能夠簡單理解爲:函數建立時,就已經根據上下文環境保存一套全部外部函數(不包含自身內部)的變量。當咱們在調用閉包函數時,閉包函數自身不存在的變量,將會在這套變量中查找。對象

值得一提
一、「變量聲明提高」對於閉包的實現是很是重要的。若是變量聲明沒有被提高,那麼咱們將沒法保存那些在閉包函數建立之後才聲明的變量。
二、閉包的機制,做用域鏈會一直引用自身之外的函數的所有變量,內存回收機制不能及時回收這些變量,從而增大內存開銷。ip

相關文章
相關標籤/搜索