維基百科:在計算機科學中,閉包(Closure),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即便已經離開了創造它的環境也不例外。因此,有另外一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。javascript
上面的解釋不免有些抽象,爲了化繁爲簡,本文將經過實例的方式,探究Javascript中閉包的概念及其用途。爲了更好地理解閉包,我將從Javascript的變量的做用域談起。html
有點相似於原型鏈(proto chain),Javascript中變量聽從做用域鏈(scope chain)規則。
如上圖所示,在Javascript中,每個函數體對應於一個做用域。當訪問一個變量時,咱們會先訪問當前做用域內是否有定義該變量,若是沒有就會在該做用域外的做用域內尋找是否有改變量,依此類推,一直尋找到全局變量。若是全局變量中依舊沒有定義該變量,就會返回undefined。java
咱們來看下下面這個例子:閉包
var milk = '外面的特侖蘇' function wrapper1() { var milk = '裏面的特侖蘇' console.log('我要喝' + milk) //我要喝裏面的特侖蘇 } function wrapper2() { console.log('我要喝' + milk) //我要喝外面的特侖蘇 } wrapper1() wrapper2()
在上述例子中,咱們在wrapper1函數體內定義了變量milk,所以wrapper1在尋找完當前做用域便可以獲得裏面的特侖蘇,而在wrapper2函數體內沒有定義變量milk,它會沿着做用域鏈去尋找全局變量,而後獲得了外面的特侖蘇。因此,有另外一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。app
按照做用域鏈的規則,咱們沒法在某函數體外訪問到該函數內的局部變量。可是出於某些目的咱們想在函數體外訪問到函數體內的局部變量,咱們該怎麼作呢?請看下面例子:函數
function wrapper1() { var milk = '裏面的特侖蘇' function drink() { console.log('我喝了' + milk) } return drink } var result = wrapper1() result() //我喝了裏面的特侖蘇
咱們在函數體內再建立一個函數,而且把這個內部函數drink做爲外部函數wrapper1的返回值。咱們經過執行函數wrapper1得到了它的返回值drink,而且執行它,就成功的訪問到了它的內部變量milk(裏面的特侖蘇)。
回想維基百科中閉包的定義,再結合上述例子:drink函數就是一個閉包,由於它引用了處於它外部的變量milk。這個被引用的外部變量milk和函數drink一同存在,即便已經離開了創造它的環境也不例外。因此,有另外一種說法認爲閉包是由函數和與其相關的引用環境組合而成的實體。學習
從上述例子中不難看出,閉包的一個用途就是能夠訪問函數的內部變量。從而能夠實現一些面向對象的功能,例如設置類的隱私變量,關於這一點能夠參考《對Javascript 類、原型鏈、繼承的理解》。
閉包的另外一個用途就是可使變量一直保存在內存之中,不被垃圾回收機制所回收。看下面這個例子:code
var change; function wrapper() { var milk = '特侖蘇' function drink() { console.log('我喝了' + milk) } change = function () { milk = 'AD鈣奶' } return drink } var result = wrapper() result() //我喝了特侖蘇 change() result() //我喝了AD鈣奶
能夠看到,wrapper執行以後,milk變量一直能被訪問到,緣由就是result引用了wrapper內部的drink函數,drink函數又引用了milk變量,所以它一直不會被垃圾回收機制所回收。htm
由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然可能會形成內存泄露。解決方法是,在使用完閉包函數以後,將變量設置爲undefined。好比在上例中,在使用完result以後,將result設置爲null或者undefined。對象