我最初對閉包的定義
關于閉包,很多地方都有所謂的標準定義;但我相信很多人和我一樣,看了標準定義之后就進入了蒙B狀態。下面是我給出的定義;
閉:封閉
包:作用域
閉包:封閉的作用域
我知道看完我的定義你一樣進入到了蒙B狀態,但是沒關系,且聽我慢慢道來。
你一定想問我,是不是只要有一個封閉的作為域就能形成閉包呢?我很想說Yes,但是我不能。因為是不是閉包,還需要看它能否表現出閉包的特性。
關于閉包的特性,在基礎里是說不完的,稍候我會列舉幾個閉包基本的特性;關于閉包特性的全面分析,我會再出專門的文章來講述我的理解。
說閉包,就必須說清楚它所處的語言環境,在不同的語言環境下,閉包的特性是不完全一樣的。所以我定的標題是JS閉包,在javascript語言環境下的閉包。
看了對閉包的標準定義后我的疑問
現在,不得不引用一些標準定義,來簡化我的表達:
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 就是一個關聯了“獨立(自由)變量”(這些變量在封閉的作用域內定義,并且只能在這個封閉的作用域內使用)的函數;也就是說,作為閉包的函數記住了創建他們的上下文環境。
關于這個定義是否準確,在此我先不發表意見,但對于以上定義,我有以下疑問:
- 什么叫獨立變量或自由變量?
- 怎么記住的?
- 變量只能在封閉的作用域內使用,這個封閉的作用域與閉包是什么關系?
并且我相信很多人與我一樣,有這些疑問。接下來我就一一解釋這些疑問,當所有的疑問被消除后,你應該就能對閉包有一個基本的認知了。
上面的英文在括號中有對“獨立(自由)變量”進行解釋說明,但這句話非常具有誤導性,想要真正理解閉包,你必須忘了它。
通過其他版本的閉包定義消除第一層疑問
好,我們再來看看下而來自于“必應網典”對閉包的定義:
閉包是指可以包含自由(未綁定到特定對象)變量的代碼塊;這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變量)。“閉包” 一詞來源于以下兩者的結合:要執行的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和為自由變量提供綁定的計算環境(作用域)。
通過上面這段來自于必應網典的定義,我們可以把上面的第一個疑問消除了。下面給出我的理解:
獨立(自由)變量:
- 首先是未綁定到特定的對象,也就是無法使用
obj.attr
的方式訪問到的變量。(obj
是一個對象,而attr
是獨立變量的變量名) - 不是在這個代碼塊內或者全局上下文件中定義的,而是在定義代碼塊的環境中定義的局部變量。
如果你對“什么是綁定到特定對象”不了解,你可以去看一下JS中是如何支持面向對象的,相信你看完后會明白“什么是綁定到特定的對象”。
我相信有人會問,上面 2
中提到的“代碼塊”是什么?其實我最初讀“必應網典”上的這段文字時也有同樣的疑問。
請再讀一遍必應網典上的定義閉包的那段話吧,第一句:
閉包是指可以包含自由(未綁定到特定對象)變量的“代碼塊”;
然后繼續往下讀……,是不是明白了,這段話中的“代碼塊”其實就是指閉包。那我們把“代碼塊”三個字換成“閉包”然后再來讀一下 2
里面的話:
不是在這個“閉包”內或者全局上下文件中定義的,而是在定義“閉包”的環境中定義的局部變量。
什么是“局部變量”我就不再解釋了,如果你不懂,那么請放棄編程;經過以上分析我們對“獨立(自由)變量”的含意已經非常清楚了。下面再重寫一遍,加深印象。
獨立(自由)變量:
- 首先是未綁定到特定的對象
- 不是在這個“閉包”內或者全局上下文件中定義的,而是在定義“閉包”的環境中定義的局部變量。
通過分析推理消除第二層疑問
好了,疑問一已經消除,那就向疑問二進軍吧。先看一段閉包的代碼:
function ClosuresOuter(){
var random = Math.random();
function ClosuresInner(){
return random
}
return ClosuresInner;
}
Closures = ClosuresOuter();
請仔細思考后告訴我,上面這段代碼中,誰是閉包?
我知道有人說 ClosuresOuter 是閉包,也有人說 ClosuresInner 是閉包;這樣理解也不能說是錯,但真正意義上的閉包應該是上述代碼中的 Closures ,通過調用 ClosuresOuter 返回的函數。
有爭議,沒關系,我們先不討論誰才是真正意義上的閉包,我們回到最初的目的,解決疑問二:閉包是如何記住獨立(自由)變量的?
不過要先確認一點,所有人都認同以上代碼中是創建了閉包的。如果你覺得上述代碼沒有創建閉包,那么請回避。
在函數內可以訪問函數外聲明的變量,看上面的代碼,ClosuresInner 內部使用了在 ClosuresInner 外部定義的變量 random ,所以如果ClosuresInner 是閉包的話,它就是這樣記住“獨立(自由)變量”的。
在javascript中,上面代碼中的ClosuresInner 不僅記住了 random, 還記住了運行函數 ClosuresOuter 所創建的整個環境,這是javascript的語言特性,也是javascript支持閉包特性的前提條件。
現在疑問二告破;
究竟誰是閉包?
在上一節的代碼中,random 是否滿足“獨立(自由)變量”的條件呢?
- 首先這個變量沒有綁定到任何對象
- 然后這個變量不能在閉包內或全局作用域內定義,需要在定義閉包的作用域內定義。
好吧,ClosuresOuter 已經不可能是閉包了。有些認為ClosuresInner是閉包的同學們是不是開始洋洋得意,沾沾自喜了?不要這樣,請往下看。
請仔細看上面的“實驗截圖”,ClosureOuter 就是上面代碼中定義的那個。每次運行 ClosureOuter 都會產生一個新的 random ,請問是誰記住了random?答案是 ClosureInner,但也不是。原因是,每次運行ClosureOuter 同樣會產生一個新的 ClosureInner。
這樣說吧,ClosureInner其實并不存在,它只是ClosureOuter 作用域內的一個局部變量(函數);如果這個ClosureInner沒有被 ClosureOuter 返回并被外層接收返回值的變量接收的話,這個ClosureOuter 運行所創建的臨時作用域和作用域內的變量(包含 random 和ClosureInner)會很快被垃圾回收器收回。
所以結論是 ClosureOuter 返回的函數才是閉包(也就是上述代碼中的 Closure)。
閉包的基本特性
這個小節只是為了履行前方的承諾,所以不打算用心寫,見諒!
- 記住閉包所在的環境;創建閉包的外層函數運行時所創建的環境不會被垃圾回收,只有這樣才能讓閉包記住它所在的環境以及該環境內的獨立(自由)變量;所以使用閉包有得有失啊。
自圓其說
最開始我對閉包的定義是:封閉的作用域。這明顯很不靠譜啊。所以現在細化如下:
閉:封閉
包:作用域,環境
閉包:能夠記憶被創建時環境的封閉作用域
好了,也是址淡的定義,初學者看后也只能蒙了一B。
我為什么不說閉包是“函數”,而是說成“作用域”
簡單了說,是因為我知道java的“內部類”也是一種閉包。
裝B一點的說法是:只要能夠表現出閉包的特性,就可以稱之為閉包。在javascript中只有函數有自己的作用域,所以也只有函數有條件成為閉包。但在其他語言中有很多擁有自己作用域的概念,如:包,類,一個代碼塊都可以有自己的作用域。所以把說成是擁有閉包特性的函數也只在javascript語言中成立。
總結
之所以有這個小節,是我的一種習慣。
閉包基礎就寫到這兒吧。有不同的見解的,歡迎吐槽。
為什么不解釋疑問三
我都說了這句話:“這些變量在封閉的作用域內定義,并且只能在這個封閉的作用域內使用”,非常具有誤導性,想要真正理解閉包,你必須忘了它。你為什么不聽呢?哼!