前言:還是一篇入門文章。 Javascript中有幾個非常重要的語言特性──物件、原型繼承、閉包。其中閉包 對於那些使用傳統靜態語言C/C 的程式設計師來說是一個新的語言特性。本文將以範例入手來介紹Javascript閉包的語言特性,並結合一點 ECMAScript語言規範來使讀者可以更深入的理解閉包。
註:本文是入門文章,範例素材整理於網絡,如果你是高手,歡迎針對文章提出技術性建議和意見。本文討論的是Javascript,不想做語言對比,如果您對Javascript天生不適,請自行繞道。
什麼是閉包
閉包是什麼?閉包是Closure,這是靜態語言所不具有的一個新特性。但閉包也不是什麼複雜到不可理解的東西,簡而言之,閉包就是:
閉包就是函數的局部變數集合,只是這些局部變數在函數傳回後會繼續存在。
閉包就是函數的「堆疊」在函數回傳後並不會釋放,我們也可以理解為這些函數堆疊並不在堆疊上分配而是在堆上分配
當在一個函數內定義另一個函數就會產生閉包
上面的第二個定義是第一個補充說明,抽取第一個定義的主謂賓-閉包是函數的‘局部變數'集合。只是這個局部變數是可以在函數返回後被存取。 (這個不是官方定義,但這個定義應該更有利於你理解閉包)
做為局部變數都可以被函數內的程式碼訪問,這個和靜態語言是沒有差別。閉包的差異在於局部變變數可以在函數執行結束後仍然被函數外的程式碼存取。這意味著 著函數必須傳回一個指向閉包的“引用”,或將這個”引用”賦值給某個外部變量,才能保證閉包中局部變數被外部程式碼存取。當然包含這個引用的實體應該是一個 對象,因為在Javascript中除了基本型別剩下的就都是對象了。可惜的是,ECMAScript並沒有提供相關的成員和方法來存取閉包中的局部變 量。但是在ECMAScript中,函數物件中定義的內部函數(inner function)是可以直接存取外部函數的局部變量,透過這個機制,我們就可以以如下的方式完成對閉包的存取了。
上述程式碼的執行結果是:Hello Closure,因為sayHello()函式在greeting函式執行完畢後,仍然可以存取到了定義在其之內的局部變數text。
好了,這個就是傳說中閉包的效果,閉包在Javascript中有多種應用場景和模式,比如Singleton,Power Constructor等這些Javascript模式都離不開對閉包的使用。
ECMAScript閉包模型
ECMAScript到底是如何實現閉包的呢?想深入了解的親們可以取得ECMAScript 規範進行研究,我這裡也只做一個簡單的講解,內容也是來自於網路。
在ECMAscript的腳本的函數運行時,每個函數關聯都有一個執行上下文場景(Execution Context) ,這個執行上下文場景中包含三個部分
文法環境(The LexicalEnvironment)
變數環境(The VariableEnvironment)
this綁定
其中第三點this綁定與閉包無關,不在本文討論。文法環境中用於解析函數執行過程使用到的變數標識符。我們可以將文法環境想像成一個對象,該對 象包含了兩個重要元件,環境記錄(Enviroment Recode),和外部引用(指標)。環境記錄包含包含了函數內部聲明的局部變數和參數變量,外部引用指向了外部函數物件的上下文執行場景。全域的上下文 場景中此引用值為NULL。這樣的資料結構就構成了一個單向的鍊錶,每個引用都指向外層的上下文場景。
例如上面我們例子的閉包模型應該是這樣,sayHello函數在最下層,上層是函數greeting,最外層是全域場景。如下圖: 因此當sayHello被呼叫的時候,sayHello會透過上下文場景找到局部變數text的值,因此在螢幕的對話框中顯示出”Hello Closure” 變數環境(The VariableEnvironment)和文法環境的作用基本相似,具體的差異請參考ECMAScript的規範文件。
閉包的樣本列
前面的我大致了解了Javascript閉包是什麼,而閉包在Javascript是怎麼實現的。以下我們透過針對一些例子來幫助大家更深入的理解閉包,以下共有5個範例,範例來自於JavaScript Closures For Dummies(鏡像)。 例1:閉包中局部變數是引用而非拷貝
因此執行結果應該彈出的667而非666。
範例2:多個函數綁定同一個閉包,因為他們定義在同一個函數內。
例子3:當在一個循環中賦值函數時,這些函數會綁定相同的閉包
testList的執行結果是彈出item3 undefined視窗三次,因為這三個函數綁定了同一個閉包,而且item的值為最後計算的結果,但是當i跳出循環時i值為4,所以list [4]的結果為undefined.
範例4:外部函數所有局部變數都在閉包內,即使這個變數宣告在內部函數定義之後。
執行結果是彈出」Hello Alice」的視窗。即使局部變數宣告在函數sayAlert之後,局部變數仍然可以被存取。
範例5:每次函數呼叫的時候建立一個新的閉包
閉包的應用
Singleton 單件:
這個單件透過閉包來實現。透過閉包完成了私有的成員和方法的封裝。匿名主函數傳回一個物件。物件包含了兩個方法,方法1可以方法私有變量,方法2訪 問內部私有函數。需要注意的地方是匿名主函數結束的地方的'()',如果沒有這個'()'就不能產生單件。因為匿名函數只能傳回了唯一的對象,而且不能被 其他地方呼叫。這個就是利用閉包產生單件的方法。