javascript閉包

前言

  閉包對於初學者而言一直是一個不太好理解的概念。最近在學習javascript的時候碰巧看到了關於這方面的講解,本身才明白了許多,因此把它寫出來分享給你們。固然,本文也是參考了不少blog和書籍,加上本身的理解寫出來的,文章末尾會附上對應的參考文檔。javascript

基礎知識

//javascript的變量做用域能夠分爲兩種:全局變量和局部變量。
//在函數內聲明的變量就是局部變量,這個變量在函數體內可訪問,在函數外部沒法直接讀取局部變量。
//例如:
var globalVariable = 1; //全局變量
function f() {
    var localVariable = 100; //局部變量 注意函數內的變量必定要加上關鍵字var才能成爲局部變量,否則就會成爲全局變量
}
alert(globalVariable); //顯示1
alert(localVariable);  //拋出錯誤 提示localVariable未聲明
變量做用域
//javascript內部函數又稱爲嵌套函數,就是在函數中聲明的函數
//函數能夠實現多級嵌套
//例如:
function f() {
    function innerFunction() {
        //innerFunction是f的內部函數
        function anotherInnerFunction() {
            //anotherInnerFunction是innerFunction的內部函數
        }
    }
}
內部函數
//局部變量對於內部函數而言是可訪問的,整個做用域鏈只能向下傳遞。
//例如:
function f() {
    var localVariable = 100; //局部變量
    function innerFunction() {
        var anotherLocalVariable = 200; //局部變量
        alert(localVariable);//內部函數能夠訪問外部函數的局部變量
    }
    alert(anotherLocalVariable); //拋出錯誤 提示anotherLocalVariable未聲明
}
變量做用域
//javascript借鑑了函數式編程的概念,把函數提到了一等公民的地位,意思是函數能夠跟變量同樣被賦值,做爲傳入參數和返回參數。
//例如:
function f1() {
}
var temp1 = f1; //函數能夠賦值給變量

function f2(f) {
    f();
}
f2(f1); //函數f1能夠做爲函數f2的參數

function f3() {
    function f4() {
    }
    return f4; //函數f4能夠做爲函數f3執行後返回的參數
}
var temp2 = f3(); //temp2的值是f4
函數是第一公民

閉包的概念

  若是單純從字面上來理解"閉包"這兩個字,每每讓人摸不着頭腦,因此咱們能夠更多地把"閉包"看成一個代號名稱,不要從字面上理解這個概念。參考了多處資料,發現閉包的官方定義很晦澀難懂,通過個人理解,以爲閉包的概念能夠這樣描述:當一個內部函數被調用,就會造成閉包。看代碼應該會更清晰一些:html

//在聲明它的函數中調用
function f() {
    var localVariable = 100; //局部變量
    function innerFunction() {
        //訪問局部變量
        localVariable += 1;
        alert(localVariable);
    }
    innerFunction();
}
f(); //顯示101
//這樣子確實也造成了閉包,但暫時沒發現這樣作有什麼意義


//在聲明它的函數外部調用
function f() {
    var localVariable = 100; //局部變量
    function innerFunction() {
        //訪問局部變量
        localVariable += 1;
        alert(localVariable);
    }
    return innerFunction; //innerFunction做爲函數f的返回參數
}
var temp = f(); 
temp(); //顯示101
temp(); //顯示102
//可見,閉包使得函數外部操做局部變量變爲可能
//假如咱們只是單純執行f(),不把結果賦值給全局變量temp,那麼執行完後,因爲不存在其餘引用,這時候函數f就會成爲垃圾回收的對象
//而因爲賦值給全局變量temp了,innerFunction引用到f函數,函數f的局部變量localVariable就不會被回收,因此就出現了上文中執行兩次temp(),實現累加效果
內部函數可使用兩種調用方式

閉包的應用場景

  單純地知道閉包的概念是遠遠不夠的,要充分理解閉包這個概念,還得從它的使用場景入手。java

  其實閉包的應用能夠簡單總結爲一句話:將變量維持在內存,帶來更好的安全性和封裝性。就像在上一節中使用的demo同樣,將localVariable維持在內存中,使得改變它變爲可能。其實,要實現這個功能,你可能以爲使用全局變量就能夠了,除非從新加載或關閉頁面,不然全局變量一直在內存中。可是使用全局變量其實不是一種很差的作法,最主要是由於全局變量是能夠手動更改的,例如定義一個globalVariable,你在接下來的代碼能夠爲所欲爲地修改,而使用閉包,想要修改變量,就只能經過調用已經實現的特定函數,學過面向對象的人都知道,這就是良好封裝性的體現,不能隨意修改,就保證了變量的安全性。例以下面的demo:node

function f() {
    var localVariable = 100; //局部變量
    function innerPlus() {
        localVariable += 1;
        alert(localVariable);
    }
    function innerMinus() {
        localVariable -= 1;
        alert(localVariable);
    }
    return {
        plus: innerPlus,
        minus:innerMinus
    }; //此次返回的再也不是一個單獨的函數,而是一個對象,對象的屬性是兩個內部函數
}
var temp = f();
temp.plus();  //顯示101
temp.minus(); //顯示100
//只能經過temp的特定API操做變量,實現了封裝性和更好的安全性
Demo

  這裏再補充一個Demo,關於內部函數在聲明它的函數中調用的實際應用場景。編程

//對node節點的背景顏色作一次漸變
function fadeBgColor(node) {
    var level = 1;
    function fade() {
        if (level < 15) {
            var hex = level.toString(16);//將整型轉化爲16位的字符 如10對應a
            node.style.backgroundColor = '#FFFF' + hex + hex;
            level += 1;//訪問局部變量
            setTimeout(fade, 200);//遞歸調用自身
        }
    }
    setTimeout(fade, 200);//200ms後執行fade函數
}
fadeBgColor(document.body);
內部函數在聲明函數中調用Demo

深刻理解閉包

  接下來的這一小節將從閉包的微觀世界提及,展現一段javascipt代碼,經過對javascript解析器執行原理的剖析,逐步地探討閉包的內部機理。安全

function f() {
    var localVariable = 100;
    function innerFunction() {
        localVariable += 1;
        alert(localVariable);
    }
    return innerFunction;
}
var temp = f();
temp();
Demo

  1. javascript解析器會在頁面加載後建立一個全局執行上下文(Execution Context),全局執行上下文的做用域鏈中只有一個全局對象,它定義了javascript中全部可用的全局變量和函數,全局對象在初始化的時候會包含this,window,document等對象,全局對象會隨着javascript的執行動態地新增對象。網絡

  2. 當javascript解析器執行到函數聲明function f(){...}的時候,由於函數f是一個全局函數,因此會把全局執行上下文的做用域鏈賦給函數f的內部屬性scope(內部屬性沒法經過javascript直接訪問)。閉包

  3. 接下來var temp = f(),這時javascript解析器會執行函數f,同時建立一個對應的執行上下文,並建立一個活動對象,並初始化給this和函數傳入參數arguments,同時加入函數f中全部的變量(初始化爲undifined),隨着函數的執行變量將逐步被賦值。活動對象會出如今執行上下文做用域鏈的頂端,緊接着是f函數的scope屬性中的對象。ide

  

  4. 執行f函數的時候,javascript解析器會完成函數innerFunction的聲明,這時innerFunction的執行上下文是函數f的執行上下文,因此會把函數f的執行上下文做用域鏈賦值給innerFunction的scope屬性。函數式編程

  5. 最後執行temp(),其實就是執行innerFunction(),這時候會建立一個對應的新的執行上下文,同時建立temp函數的活動對象。一樣活動對象會出如今做用域鏈的頂端,接下來是scope屬性的做用域鏈。

  

  此時,函數f從聲明到執行的步驟就完成了。最終函數temp的做用域鏈引用着函數f的活動對象,因此在函數f返回後就不會被GC回收。從這裏也能夠清晰地看到,函數temp在查找localVariable變量的時候,首先先從自身的活動對象中查找,找不到再到原型對象(有關原型對象這裏就暫時不討論了)中找,以後就到函數f的活動對象中找,再找不到就到全局對象中查找。

  若是你有什麼新的想法,歡迎留言討論。

參考文獻: 阮一峯的網絡日誌-學習javascript閉包,javascript權威指南第8章閉包,高性能網站建設進階指南第7章編寫高效的javascript,閉包維基百科,javascirpt語言精髓4.8節

修改記錄: [2014.8.25 第一次修改 主要修改了閉包的概念] [2014.9.23 第二次修改 主要修改了活動對象的描述] [2014.11.28 第三次修改 新增內部函數在聲明它的函數中調用的案例]

相關文章
相關標籤/搜索