維基百科:在計算機科學中,閉包(Closure),是引用了自由變量的函數(shù)。這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認為閉包是由函數(shù)和與其相關的引用環(huán)境組合而成的實體。
上面的解釋難免有些抽象,為了化繁為簡,本文將通過實例的方式,探究Javascript中閉包的概念及其用途。為了更好地理解閉包,我將從Javascript的變量的作用域談起。
一、Javascript變量的作用域
有點類似于原型鏈(proto chain),Javascript中變量遵從作用域鏈(scope chain)規(guī)則。

如上圖所示,在Javascript中,每一個函數(shù)體對應于一個作用域。當訪問一個變量時,我們會先訪問當前作用域內(nèi)是否有定義該變量,如果沒有就會在該作用域外的作用域內(nèi)尋找是否有改變量,依此類推,一直尋找到全局變量。如果全局變量中依舊沒有定義該變量,就會返回undefined。
我們來看下下面這個例子:
var milk = '外面的特侖蘇'
function wrapper1() {
var milk = '里面的特侖蘇'
console.log('我要喝' + milk) //我要喝里面的特侖蘇
}
function wrapper2() {
console.log('我要喝' + milk) //我要喝外面的特侖蘇
}
wrapper1()
wrapper2()
在上述例子中,我們在wrapper1函數(shù)體內(nèi)定義了變量milk,因此wrapper1在尋找完當前作用域即可以得到里面的特侖蘇,而在wrapper2函數(shù)體內(nèi)沒有定義變量milk,它會沿著作用域鏈去尋找全局變量,然后得到了外面的特侖蘇。所以,有另一種說法認為閉包是由函數(shù)和與其相關的引用環(huán)境組合而成的實體。
二、閉包的概念
按照作用域鏈的規(guī)則,我們無法在某函數(shù)體外訪問到該函數(shù)內(nèi)的局部變量。但是出于某些目的我們想在函數(shù)體外訪問到函數(shù)體內(nèi)的局部變量,我們該怎么做呢?請看下面例子:
function wrapper1() {
var milk = '里面的特侖蘇'
function drink() {
console.log('我喝了' + milk)
}
return drink
}
var result = wrapper1()
result() //我喝了里面的特侖蘇
我們在函數(shù)體內(nèi)再創(chuàng)建一個函數(shù),并且把這個內(nèi)部函數(shù)drink作為外部函數(shù)wrapper1的返回值。我們通過執(zhí)行函數(shù)wrapper1獲得了它的返回值drink,并且執(zhí)行它,就成功的訪問到了它的內(nèi)部變量milk(里面的特侖蘇)。
回想維基百科中閉包的定義,再結(jié)合上述例子:drink函數(shù)就是一個閉包,因為它引用了處于它外部的變量milk。這個被引用的外部變量milk和函數(shù)drink一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認為閉包是由函數(shù)和與其相關的引用環(huán)境組合而成的實體。
三、閉包的用途
從上述例子中不難看出,閉包的一個用途就是可以訪問函數(shù)的內(nèi)部變量。從而可以實現(xiàn)一些面向?qū)ο蟮墓δ?,例如設置類的隱私變量,關于這一點可以參考《對Javascript 類、原型鏈、繼承的理解》。
閉包的另一個用途就是可以使變量一直保存在內(nèi)存之中,不被垃圾回收機制所回收。看下面這個例子:
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執(zhí)行之后,milk變量一直能被訪問到,原因就是result引用了wrapper內(nèi)部的drink函數(shù),drink函數(shù)又引用了milk變量,因此它一直不會被垃圾回收機制所回收。
四、慎用閉包
因為閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則可能會造成內(nèi)存泄露。解決方法是,在使用完閉包函數(shù)之后,將變量設置為undefined。比如在上例中,在使用完result之后,將result設置為null或者undefined。