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