摘要:閉包在很多javascript高級應(yīng)用中都會出現(xiàn)。本文主要以javascript語言為例分析閉包現(xiàn)象的形成,理解閉包的運(yùn)行機(jī)制及使用。
關(guān)鍵詞:閉包;內(nèi)部變量;作用域
中圖分類號:TP312.2 文獻(xiàn)標(biāo)識碼:A 文章編號:1007-9599 (2012) 23-0000-02
閉包問題是英格蘭Brighton ALT.NET Beers活動中提出來的。閉包的概念很抽象,如果不用代碼來說明,將很難用描述性語句把它解釋清楚。所以本文將以javascript語言為例解釋說明什么是閉包,分析閉包的運(yùn)行機(jī)制及使用注意事項(xiàng)。
1 閉包的概念
什么是閉包?在計(jì)算機(jī)科學(xué)中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。ECMAScript允許使用內(nèi)部函數(shù)--即函數(shù)定義和函數(shù)表達(dá)式位于另一個(gè)函數(shù)的函數(shù)體內(nèi)。[1]而且,這些內(nèi)部函數(shù)可以訪問它們所在的外部函數(shù)中聲明的所有局部變量、參數(shù)和聲明的其他內(nèi)部函數(shù)。當(dāng)其中一個(gè)這樣的內(nèi)部函數(shù)在包含它們的外部函數(shù)之外被調(diào)用時(shí),就會形成閉包。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。由于在Javascript語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡單理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)”。
2 理解閉包在javascript中的運(yùn)行及使用
2.1 如何理解閉包
閉包的創(chuàng)建相對容易,有時(shí)創(chuàng)建閉包編程人員根本沒有意識到這是閉包,尤其是在IE等常見的瀏覽器環(huán)境下運(yùn)行下,所編寫的程序在很大程度上存在潛在的問題。因此在編寫JAVASCRIPT高級應(yīng)用程序是,對閉包的使用和它的運(yùn)行機(jī)制必須有一定了解。而了解閉包運(yùn)行機(jī)制的就要先解析在編寫過程中的環(huán)境變量及作用域。
javascript中每個(gè)函數(shù)都是一個(gè)函數(shù)對象(函數(shù)實(shí)例),既然是對象,就有相關(guān)的屬性和方法。[scope]就是每個(gè)函數(shù)對象都具有的一個(gè)僅供javascript引擎內(nèi)部使用的屬性,該屬性是一個(gè)集合(類似于鏈表結(jié)構(gòu)),集合中保存了該函數(shù)在被創(chuàng)建時(shí)的作用域中的所有對象,而這個(gè)作用域集合形成的鏈表則被稱為ScopeChain(作用域鏈)[2]。該作用域鏈中保存的作用域?qū)ο?,就是該函?shù)可以訪問的所有數(shù)據(jù)。例如定義一個(gè)函數(shù)求兩個(gè)數(shù)的和運(yùn)算
當(dāng)add函數(shù)被創(chuàng)建時(shí),函數(shù)所在的全局作用域的全局對象被放置到add函數(shù)的作用域鏈([[scope]]屬性)中。從圖2-1中看到作用域鏈的第一個(gè)對象保存的是全局對象,全局對象中保存了諸如this,window,document以及全局對象中的add函數(shù)。這也就是為什么可以在在全局作用域下的函數(shù)中訪問window(this),訪問全局變量,訪問函數(shù)自身的原因。當(dāng)局部變量和全局變量同名時(shí),會使用局部變量而不使用全局變量,
2.2 全局變量在javascript函數(shù)中的應(yīng)用
分析Javascript特殊的變量作用域是可以方便編程人員理解閉包。在編程語言中,所有的變量的作用域從函數(shù)的角度來說都可以分成:全局變量和局部變量。而Javascript語言的是比較特殊的一種語言,它可以從函數(shù)內(nèi)部可以直接讀取并引用全局變量。一個(gè)閉包就是一個(gè)引用了其被生成的環(huán)境中、所屬的變量范圍內(nèi)的所有變量的函數(shù)。[3]例如定義了一個(gè)函數(shù)f1,代碼如下:
var x = 1;
function f1()
{var y = 1;
var result = x + y;
document.wirte(“result=”, result);
};
這里我們首先定義了一個(gè)變量“x”,值為1。然后我們定義無參無返回值函數(shù)f1,在f1函數(shù)內(nèi)部result中使用了“x”變量。這個(gè)變量是被f1 引用了,自動被添加到了f1的運(yùn)行環(huán)境中了。
當(dāng)我們執(zhí)行f1時(shí),它會輸出了一個(gè)預(yù)期的結(jié)果2。但函數(shù)f1執(zhí)行時(shí),原始的“x”此時(shí)已經(jīng)不在是它當(dāng)初的變量環(huán)境,但它仍然能被使用。
2.3 局部變量在javascript函數(shù)中的應(yīng)用
在Javascript語言中,函數(shù)內(nèi)部的子函數(shù)可以直接讀取局部變量,所以閉包也可以說成是“嵌套定義在一個(gè)函數(shù)內(nèi)部的函數(shù)”。而閉包就成為了函數(shù)內(nèi)部和函數(shù)外部相聯(lián)系一個(gè)媒介。
因此,有時(shí)候當(dāng)需要得到函數(shù)內(nèi)的局部變量??梢栽诤瘮?shù)的內(nèi)部,再定義一個(gè)函數(shù)。例如定義了一個(gè)函數(shù)f2代碼如下:
function f2()
{n=1;
function f3()
{var s=n+2
alert(“s=”+ s);//s的結(jié)果=3 }
return f3;
}
在代碼中,函數(shù)f3就被定義在函數(shù)f2內(nèi)部,這時(shí)函數(shù)f2內(nèi)部的所有局部變量,對函數(shù)f3都是有效的。即n可以被f3函數(shù)引用,但是f3內(nèi)部的局部變量,對f2就是不可見的。即s變量在f2函數(shù)中不可見。這就是Javascript語言中的“chain scope”,計(jì)算機(jī)專業(yè)術(shù)語稱為“鏈?zhǔn)阶饔糜颉?,在鏈?zhǔn)阶饔糜蛑凶訉ο髸饘酉蛏蠈ふ宜懈笇ο蟮淖兞?。所以,父對象的所有變量,對子對象都是有效的,子對象可以隨意使用父對象的變量,但是子對象中的變量卻屏蔽了,父對象沒法使用它的變量。
在代碼運(yùn)行中,函數(shù)f3可以使用f2中的局部變量n,但父對象函數(shù)f2想要讀取f3的內(nèi)部變量怎么辦?這里只要把f3作為返回值,則函數(shù)f2即可讀取到內(nèi)部變量s的值。這里的f3()就是一個(gè)閉包。
2.4 閉包在javascript語言中的應(yīng)用
閉包可以用在許多地方。它的最大用處有兩個(gè),一個(gè)是前面提到的可以讀取函數(shù)內(nèi)部的變量,另一個(gè)就是讓這些變量的值始終保持在內(nèi)存中。例如定義一個(gè)函數(shù)a,在函數(shù)a內(nèi)部定義如下函數(shù)b,代碼如下:
function a() //外層函數(shù)a
{ var n=1; //臨時(shí)變量n
function b()//內(nèi)層函數(shù)b
{
alert(n++);//引用外層臨時(shí)變量n
}
return b; }//返回函數(shù)b
var result=a (); //調(diào)用函數(shù)a,函數(shù)b被引用
result(); //函數(shù)b中n變量被引用值為2.
}
在這段代碼中,函數(shù)b實(shí)際上就是一個(gè)閉包函數(shù)。在程序代碼中函數(shù)b被調(diào)用了兩次,變量n值第一次的n=1,第二次的n=2。這說明了,函數(shù)a中的局部變量n一直駐留在內(nèi)存中,當(dāng)函數(shù)a調(diào)用后沒有被釋放,第二次調(diào)用時(shí)仍然再使用。這種情況說明,函數(shù)b可以引用函數(shù)a的全局變量,在運(yùn)行中函數(shù)b始終在內(nèi)存中,而函數(shù)b的存在依賴于函數(shù)a,在調(diào)用結(jié)束后,函數(shù)a也會駐留在內(nèi)存,不會被javascript的垃圾回收機(jī)制回收。
從上面的例子我們得出以下在javascript中使用閉包是的結(jié)論:
(1)使用閉包可以使函數(shù)內(nèi)的變量更加安全,防止其他變量通過任何其他路徑訪問該變量。例如上例,變量n只有函數(shù)b才能訪問,而其他函數(shù)想要訪問函數(shù)a中的n變量時(shí)不可能的。
(2)使用閉包可以使變量在內(nèi)存中駐留。方便函數(shù)的調(diào)用和使用該變量。依然如上例,由于該代碼創(chuàng)建了一個(gè)閉包,當(dāng)每次執(zhí)行result()時(shí),n都會被累加。
(3)使用閉包可以封裝JS私有屬性和私有方法,使函數(shù)內(nèi)部的變量不會被外部訪問。
3 閉包應(yīng)用中的注意事項(xiàng)
(1)合理利用和創(chuàng)建閉包。閉包會使函數(shù)中的變量長久保存內(nèi)存中,消耗內(nèi)存的資源,造成網(wǎng)頁崩潰等性能問題,從而導(dǎo)致IE中內(nèi)存泄露等。所以在編寫該代碼運(yùn)行后,要將不使用的局部變量刪除。
(2)閉包很輕易的可以改變父函數(shù)內(nèi)部變量值,所以謹(jǐn)慎使用閉包。在把閉包當(dāng)作父函數(shù)的公有方法或把內(nèi)部變量當(dāng)作父函數(shù)的私有屬性時(shí),父函數(shù)內(nèi)的變量值不能隨意改變,造成函數(shù)變量值的混亂。
(3)警惕和及時(shí)察覺意外閉包現(xiàn)象。前面的例子也說明了閉包很容易創(chuàng)建,在javascript中允許使用閉包,所以在沒有認(rèn)識到閉包是一種語言特性的JavaScript時(shí),編程人員會按照想法來使用內(nèi)部函數(shù)。但對使用內(nèi)部函數(shù)的結(jié)果并不明了,有可能在定義函數(shù)時(shí)已經(jīng)定義了一個(gè)閉包而沒有意識到。會使最終結(jié)果并不是編程人員向要的結(jié)果。在IE的內(nèi)存泄漏問題中,閉包意外創(chuàng)建的會存在很多潛在的技術(shù)問題,從而影響到代碼的性能及結(jié)果。
4 結(jié)束語
閉包問題不在javascript語言于是否允許使用閉包。而在于在理解了閉包的運(yùn)行機(jī)制基礎(chǔ)上,謹(jǐn)慎有效的使用閉包,才能寫出更為安全的代碼,為編寫程序提供方便。
參考文獻(xiàn):
[1]David Flanagan.JavaScript權(quán)威指南(第5版)[M].李強(qiáng).北京:機(jī)械工業(yè)出版社,2007.
[2]Nicholas C.Zakas.JavaScript高級程序設(shè)計(jì)(第3版)[M].李松峰,曹力.北京:人民郵電出版社,2012.
[3]張?jiān)品?JavaScript閉包技術(shù)及IE內(nèi)存泄漏分析[J].電腦知識與技術(shù),2008,35.
[4]鄧緒高.Javascript中變量作用域淺析[J].信息與電腦(理論版),2010.12/
[作者簡介]徐紅梅(1977.10-),碩士學(xué)位,四川職業(yè)技術(shù)學(xué)院計(jì)算機(jī)系講師。
計(jì)算機(jī)光盤軟件與應(yīng)用2012年23期