我最初對閉包的定義
關(guān)于閉包,很多地方都有所謂的標(biāo)準(zhǔn)定義;但我相信很多人和我一樣,看了標(biāo)準(zhǔn)定義之后就進(jìn)入了蒙B狀態(tài)。下面是我給出的定義;
閉:封閉
包:作用域
閉包:封閉的作用域
我知道看完我的定義你一樣進(jìn)入到了蒙B狀態(tài),但是沒關(guān)系,且聽我慢慢道來。
你一定想問我,是不是只要有一個(gè)封閉的作為域就能形成閉包呢?我很想說Yes,但是我不能。因?yàn)槭遣皇情]包,還需要看它能否表現(xiàn)出閉包的特性。
關(guān)于閉包的特性,在基礎(chǔ)里是說不完的,稍候我會(huì)列舉幾個(gè)閉包基本的特性;關(guān)于閉包特性的全面分析,我會(huì)再出專門的文章來講述我的理解。
說閉包,就必須說清楚它所處的語言環(huán)境,在不同的語言環(huán)境下,閉包的特性是不完全一樣的。所以我定的標(biāo)題是JS閉包,在javascript語言環(huán)境下的閉包。
看了對閉包的標(biāo)準(zhǔn)定義后我的疑問
現(xiàn)在,不得不引用一些標(biāo)準(zhǔn)定義,來簡化我的表達(dá):
MDN官方解釋:
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
我的翻譯:
Closures 就是一個(gè)關(guān)聯(lián)了“獨(dú)立(自由)變量”(這些變量在封閉的作用域內(nèi)定義,并且只能在這個(gè)封閉的作用域內(nèi)使用)的函數(shù);也就是說,作為閉包的函數(shù)記住了創(chuàng)建他們的上下文環(huán)境。
關(guān)于這個(gè)定義是否準(zhǔn)確,在此我先不發(fā)表意見,但對于以上定義,我有以下疑問:
- 什么叫獨(dú)立變量或自由變量?
- 怎么記住的?
- 變量只能在封閉的作用域內(nèi)使用,這個(gè)封閉的作用域與閉包是什么關(guān)系?
并且我相信很多人與我一樣,有這些疑問。接下來我就一一解釋這些疑問,當(dāng)所有的疑問被消除后,你應(yīng)該就能對閉包有一個(gè)基本的認(rèn)知了。
上面的英文在括號(hào)中有對“獨(dú)立(自由)變量”進(jìn)行解釋說明,但這句話非常具有誤導(dǎo)性,想要真正理解閉包,你必須忘了它。
通過其他版本的閉包定義消除第一層疑問
好,我們再來看看下而來自于“必應(yīng)網(wǎng)典”對閉包的定義:
閉包是指可以包含自由(未綁定到特定對象)變量的代碼塊;這些變量不是在這個(gè)代碼塊內(nèi)或者任何全局上下文中定義的,而是在定義代碼塊的環(huán)境中定義(局部變量)?!伴]包” 一詞來源于以下兩者的結(jié)合:要執(zhí)行的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和為自由變量提供綁定的計(jì)算環(huán)境(作用域)。
通過上面這段來自于必應(yīng)網(wǎng)典的定義,我們可以把上面的第一個(gè)疑問消除了。下面給出我的理解:
獨(dú)立(自由)變量:
- 首先是未綁定到特定的對象,也就是無法使用
obj.attr的方式訪問到的變量。(obj是一個(gè)對象,而attr是獨(dú)立變量的變量名) - 不是在這個(gè)代碼塊內(nèi)或者全局上下文件中定義的,而是在定義代碼塊的環(huán)境中定義的局部變量。
如果你對“什么是綁定到特定對象”不了解,你可以去看一下JS中是如何支持面向?qū)ο蟮模嘈拍憧赐旰髸?huì)明白“什么是綁定到特定的對象”。
我相信有人會(huì)問,上面 2 中提到的“代碼塊”是什么?其實(shí)我最初讀“必應(yīng)網(wǎng)典”上的這段文字時(shí)也有同樣的疑問。
請?jiān)僮x一遍必應(yīng)網(wǎng)典上的定義閉包的那段話吧,第一句:
閉包是指可以包含自由(未綁定到特定對象)變量的“代碼塊”;
然后繼續(xù)往下讀……,是不是明白了,這段話中的“代碼塊”其實(shí)就是指閉包。那我們把“代碼塊”三個(gè)字換成“閉包”然后再來讀一下 2 里面的話:
不是在這個(gè)“閉包”內(nèi)或者全局上下文件中定義的,而是在定義“閉包”的環(huán)境中定義的局部變量。
什么是“局部變量”我就不再解釋了,如果你不懂,那么請放棄編程;經(jīng)過以上分析我們對“獨(dú)立(自由)變量”的含意已經(jīng)非常清楚了。下面再重寫一遍,加深印象。
獨(dú)立(自由)變量:
- 首先是未綁定到特定的對象
- 不是在這個(gè)“閉包”內(nèi)或者全局上下文件中定義的,而是在定義“閉包”的環(huán)境中定義的局部變量。
通過分析推理消除第二層疑問
好了,疑問一已經(jīng)消除,那就向疑問二進(jìn)軍吧。先看一段閉包的代碼:
function ClosuresOuter(){
var random = Math.random();
function ClosuresInner(){
return random
}
return ClosuresInner;
}
Closures = ClosuresOuter();
請仔細(xì)思考后告訴我,上面這段代碼中,誰是閉包?
我知道有人說 ClosuresOuter 是閉包,也有人說 ClosuresInner 是閉包;這樣理解也不能說是錯(cuò),但真正意義上的閉包應(yīng)該是上述代碼中的 Closures ,通過調(diào)用 ClosuresOuter 返回的函數(shù)。
有爭議,沒關(guān)系,我們先不討論誰才是真正意義上的閉包,我們回到最初的目的,解決疑問二:閉包是如何記住獨(dú)立(自由)變量的?
不過要先確認(rèn)一點(diǎn),所有人都認(rèn)同以上代碼中是創(chuàng)建了閉包的。如果你覺得上述代碼沒有創(chuàng)建閉包,那么請回避。
在函數(shù)內(nèi)可以訪問函數(shù)外聲明的變量,看上面的代碼,ClosuresInner 內(nèi)部使用了在 ClosuresInner 外部定義的變量 random ,所以如果ClosuresInner 是閉包的話,它就是這樣記住“獨(dú)立(自由)變量”的。
在javascript中,上面代碼中的ClosuresInner 不僅記住了 random, 還記住了運(yùn)行函數(shù) ClosuresOuter 所創(chuàng)建的整個(gè)環(huán)境,這是javascript的語言特性,也是javascript支持閉包特性的前提條件。
現(xiàn)在疑問二告破;
究竟誰是閉包?
在上一節(jié)的代碼中,random 是否滿足“獨(dú)立(自由)變量”的條件呢?
- 首先這個(gè)變量沒有綁定到任何對象
- 然后這個(gè)變量不能在閉包內(nèi)或全局作用域內(nèi)定義,需要在定義閉包的作用域內(nèi)定義。
好吧,ClosuresOuter 已經(jīng)不可能是閉包了。有些認(rèn)為ClosuresInner是閉包的同學(xué)們是不是開始洋洋得意,沾沾自喜了?不要這樣,請往下看。

請仔細(xì)看上面的“實(shí)驗(yàn)截圖”,ClosureOuter 就是上面代碼中定義的那個(gè)。每次運(yùn)行 ClosureOuter 都會(huì)產(chǎn)生一個(gè)新的 random ,請問是誰記住了random?答案是 ClosureInner,但也不是。原因是,每次運(yùn)行ClosureOuter 同樣會(huì)產(chǎn)生一個(gè)新的 ClosureInner。
這樣說吧,ClosureInner其實(shí)并不存在,它只是ClosureOuter 作用域內(nèi)的一個(gè)局部變量(函數(shù));如果這個(gè)ClosureInner沒有被 ClosureOuter 返回并被外層接收返回值的變量接收的話,這個(gè)ClosureOuter 運(yùn)行所創(chuàng)建的臨時(shí)作用域和作用域內(nèi)的變量(包含 random 和ClosureInner)會(huì)很快被垃圾回收器收回。
所以結(jié)論是 ClosureOuter 返回的函數(shù)才是閉包(也就是上述代碼中的 Closure)。
閉包的基本特性
這個(gè)小節(jié)只是為了履行前方的承諾,所以不打算用心寫,見諒!
- 記住閉包所在的環(huán)境;創(chuàng)建閉包的外層函數(shù)運(yùn)行時(shí)所創(chuàng)建的環(huán)境不會(huì)被垃圾回收,只有這樣才能讓閉包記住它所在的環(huán)境以及該環(huán)境內(nèi)的獨(dú)立(自由)變量;所以使用閉包有得有失啊。
自圓其說
最開始我對閉包的定義是:封閉的作用域。這明顯很不靠譜啊。所以現(xiàn)在細(xì)化如下:
閉:封閉
包:作用域,環(huán)境
閉包:能夠記憶被創(chuàng)建時(shí)環(huán)境的封閉作用域
好了,也是址淡的定義,初學(xué)者看后也只能蒙了一B。
我為什么不說閉包是“函數(shù)”,而是說成“作用域”
簡單了說,是因?yàn)槲抑纉ava的“內(nèi)部類”也是一種閉包。
裝B一點(diǎn)的說法是:只要能夠表現(xiàn)出閉包的特性,就可以稱之為閉包。在javascript中只有函數(shù)有自己的作用域,所以也只有函數(shù)有條件成為閉包。但在其他語言中有很多擁有自己作用域的概念,如:包,類,一個(gè)代碼塊都可以有自己的作用域。所以把說成是擁有閉包特性的函數(shù)也只在javascript語言中成立。
總結(jié)
之所以有這個(gè)小節(jié),是我的一種習(xí)慣。
閉包基礎(chǔ)就寫到這兒吧。有不同的見解的,歡迎吐槽。
為什么不解釋疑問三
我都說了這句話:“這些變量在封閉的作用域內(nèi)定義,并且只能在這個(gè)封閉的作用域內(nèi)使用”,非常具有誤導(dǎo)性,想要真正理解閉包,你必須忘了它。你為什么不聽呢?哼!