閉包(closure)是Javascript語(yǔ)言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。
下面就是我的學(xué)習(xí)筆記,對(duì)于Javascript初學(xué)者應(yīng)該是很有用的。
JS中,在函數(shù)內(nèi)部可以讀取函數(shù)外部的變量
function outer(){ var localVal = 30; return localVal;}outer();//30
但,在函數(shù)外部自然無(wú)法讀取函數(shù)內(nèi)的局部變量
function outer(){ var localVal = 30;}alert(localVal);//error
這里有個(gè)需要注意的地方,函數(shù)內(nèi)部聲明變量的時(shí)候,一定要使用var命令。如果不用的話,實(shí)際上是聲明了一個(gè)全局變量。
function outer(){ localVal = 30; return localVal;}outer();
alert(localVal);//30
以上的表述,是JS變量的作用域的知識(shí),它包括全局變量和局部變量。
Javascript語(yǔ)言的特殊之處,就在于函數(shù)內(nèi)部可以直接讀取全局變量。
function outer(){ var localVal = 30;
function inner(){ alert(localVal); }
return inner; }var func = outer();func();//30
我們看到在上面的代碼中,outer函數(shù)內(nèi)又定義一個(gè)函數(shù)inner,outer函數(shù)的返回值是inner函數(shù),inner函數(shù)把localVal alert出來(lái)。
我們可以看出以上代碼的特點(diǎn):函數(shù)嵌套函數(shù),內(nèi)部函數(shù)可以引用外部函數(shù)的參數(shù)和變量,參數(shù)和變量不會(huì)被垃圾回收機(jī)制收回。
代碼中的inner函數(shù),就是閉包。簡(jiǎn)單的說(shuō),閉包(closure)就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
由于在Javascript語(yǔ)言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡(jiǎn)單理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)”。所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁。
在上面的代碼中,函數(shù)inner被包含在函數(shù)outer內(nèi)部,這時(shí)outer內(nèi)部的所有局部變量,對(duì)inner都是可見的。但是inner內(nèi)部的局部變量,對(duì)oute 是不可見的。這是Javascript語(yǔ)言特有的“鏈?zhǔn)阶饔糜颉苯Y(jié)構(gòu)(chain scope),子對(duì)象會(huì)一級(jí)一級(jí)地向上尋找所有父對(duì)象的變量。所以,父對(duì)象的所有變量,對(duì)子對(duì)象都是可見的,反之則不成立。
補(bǔ)充--JS中的函數(shù)定義
JS中定義一個(gè)函數(shù),最常用的就是函數(shù)聲明和函數(shù)表達(dá)式
Js中的函數(shù)聲明是指下面的形式:
function functionName(){ }
函數(shù)表達(dá)式則是類似表達(dá)式那樣來(lái)聲明一個(gè)函數(shù):
var functionName = function(){ }
我們可以使用函數(shù)表達(dá)式創(chuàng)建一個(gè)函數(shù)并馬上執(zhí)行它,如:
(function() { var a, b // local variables // ... // and the code})()
()();第一個(gè)括號(hào)里放一個(gè)無(wú)名的函數(shù)。
二者區(qū)別:js的解析器對(duì)函數(shù)聲明與函數(shù)表達(dá)式并不是一視同仁地對(duì)待的。對(duì)于函數(shù)聲明,js解析器會(huì)優(yōu)先讀取,確保在所有代碼執(zhí)行之前聲明已經(jīng)被解析,而函數(shù)表達(dá)式,如同定義其它基本類型的變量一樣,只在執(zhí)行到某一句時(shí)也會(huì)對(duì)其進(jìn)行解析,所以在實(shí)際中,它們還是會(huì)有差異的,具體表現(xiàn)在,當(dāng)使用函數(shù)聲明的形式來(lái)定義函數(shù)時(shí),可將調(diào)用語(yǔ)句寫在函數(shù)聲明之前,而后者,這樣做的話會(huì)報(bào)錯(cuò)。
使用閉包的好處:
-希望一個(gè)變量長(zhǎng)期駐扎在內(nèi)存當(dāng)中;
-避免全局變量的污染;
-私有成員的存在
使用自執(zhí)行的匿名函數(shù)來(lái)模擬塊級(jí)作用域
(function(){ // 這里為塊級(jí)作用域 })();
該方法經(jīng)常在全局作用域中被用在函數(shù)外部,從而限制向全局作用域中添加過(guò)多的變量和函數(shù)影響全局作用域。也可以減少如閉包這樣的對(duì)內(nèi)存的占用,由于匿名函數(shù)沒有變量指向,執(zhí)行完畢就可以立即銷毀其作用域鏈。
示例:
var test = (function(){ var a= 1; return function(){ a++; alert(a); }})();test();//2test();//3
實(shí)現(xiàn)a的自加,不污染全局。
循環(huán)給每個(gè)li注冊(cè)一個(gè)click事件,點(diǎn)擊alert序號(hào)。代碼如下:
var aLi = document.getElementByClassName("test");function showAllNum( aLi ){ for( var i =0,len = aLi.length ;i<len;i++ ){ aLi[i].onclick = function(){ alert( i );//all are aLi.length! } }}
點(diǎn)擊后會(huì)一直彈出同一個(gè)值 aLi.length 而不是123。當(dāng)點(diǎn)擊之前,循環(huán)已經(jīng)結(jié)束,i值為aLi.length。
利用閉包,建一個(gè)匿名函數(shù),將每個(gè)i存在內(nèi)存中,onclick函數(shù)用的時(shí)候提取出外部匿名函數(shù)的i值。代碼如下:
var aLi = document.getElementByClassName("test");function showAllNum( aLi ){ for( var i =0,len = aLi.length ;i<len;i++ ){ (function(i){ aLi[i].onclick = function(){ alert( i ); } })(i); }}
或者:
function showAllNum( aLi ){ for( var i =0,len = aLi.length ;i<len;i++ ){ aLi[i].onclick = (function(i){ return function(){ alert( i ); } })(i); }}
外部無(wú)法直接獲取函數(shù)內(nèi)的變量,可通過(guò)暴露的方法獲取
var info = (function(){ var _userId = 23492; var _typeId = 'item'; function getUserId(){ alert(_userId); } function getTypeId(){ alert(_typeId); }})();info.getUserId();//23492info.getTypeId();//iteminfo._userId//undefinedinfo._typeId//undefined
但是這種方式會(huì)使我們?cè)诿恳淮蝿?chuàng)建新對(duì)象的時(shí)候都會(huì)創(chuàng)建一個(gè)這種方法。使用原型來(lái)創(chuàng)建一個(gè)這種方法,避免每個(gè)實(shí)例都創(chuàng)建不同的方法。在這里不做深究(一般構(gòu)造函數(shù)加屬性,原型加方法)。
this 對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境綁定的(匿名函數(shù)中具有全局性)(this:當(dāng)前發(fā)生事件的元素),有時(shí)候在一些閉包的情況下就有點(diǎn)不那么明顯了。
代碼1:
var name = "The Window";var obj = { name : "The object", getNameFunc : function(){ return function(){ return this.name; } }}alert( obj. getNameFunc()() )//The Window
代碼2:
var name="The Window"var obj = { name : "The object", getNameFunc : function(){ var _this = this; return function(){ return _this.name; } }}alert(object.getNameFunc()());//The object
javascript是動(dòng)態(tài)(或者動(dòng)態(tài)類型)語(yǔ)言,this關(guān)鍵字在執(zhí)行的時(shí)候才能確定是誰(shuí)。所以this永遠(yuǎn)指向調(diào)用者,即對(duì)‘調(diào)用對(duì)象‘者的引用。第一部分通過(guò)代碼:執(zhí)行代碼object.getNameFunc()之后,它返回了一個(gè)新的函數(shù),注意這個(gè)函數(shù)對(duì)象跟object不是一個(gè)了,可以理解為全局函數(shù);它不在是object的屬性或者方法,此時(shí)調(diào)用者是window,因此輸出是 The Window。
第二部分,當(dāng)執(zhí)行函數(shù)object.getNameFunc()后返回的是:
function( ){ return _this.name;}
此時(shí)的_this=this。而this指向object,所以that指向object。他是對(duì)object的引用,所以輸出My Object。
總結(jié):關(guān)于js中的this,記住誰(shuí)調(diào)用,this就指向誰(shuí);要訪問閉包的this,要定義個(gè)變量緩存下來(lái)。一般喜歡var _this = this。
IE9之前,JScript對(duì)象和COM對(duì)象使用不同的垃圾收集例程,那么閉包會(huì)引起一些問題。
創(chuàng)建一個(gè)閉包,而后閉包有創(chuàng)建一個(gè)循環(huán)引用,那么該元素將無(wú)法銷毀。常見的就是dom獲取的元素或數(shù)組的屬性(或方法)再去調(diào)用自己屬性等。例如:
function handler(){ var ele = document.getElementById("ele"); ele.onclick = function(){ alert(ele.id); }}
閉包會(huì)引用包含函數(shù)的整個(gè)活動(dòng)對(duì)象,即是閉包不直接引用ele,活動(dòng)對(duì)象依然會(huì)對(duì)其保存一個(gè)引用,那么設(shè)置null就可以斷開保存的引用,釋放內(nèi)存。代碼如下:
function handler(){ var ele = document.getElementById("ele"); var id = ele.id; ele.onclick = function(){ alert(id); } ele = null;}
當(dāng)然還有其他方法,推薦此法。
當(dāng)某個(gè)函數(shù)第一次被調(diào)用時(shí),會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境(execution context)及相應(yīng)的作用域鏈,并把作用域鏈賦值給一個(gè)特殊的內(nèi)部屬性(即[[Scope]])。然后,使用this、arguncmts 和其他命名參數(shù)的值來(lái)初始化函數(shù)的活動(dòng)對(duì)象(activation object)。但在作用域鏈中,外部函數(shù)的活動(dòng)對(duì)象始終處于第二位,外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象處于第三位,……直至作為作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境。
在函數(shù)執(zhí)行過(guò)程中,為讀取和寫入變量的值,就需要在作用域鏈中查找變量。來(lái)看下面的例子:
function compare(valael, value2){
if (valuel < value2){
return -1; } else if (vaiuel > value2){
return 1; } else { return 0; }}var result = compare(5, 10);
以上代碼先定義了compare()函數(shù),然后又在全局作用域中調(diào)用了它。當(dāng)?shù)谝淮握{(diào)用compare()時(shí),會(huì)創(chuàng)建一個(gè)包含this、arguments、valuel和value2的活動(dòng)對(duì)象。全局執(zhí)行環(huán)境的變量對(duì)象 (包含this、result和compare)在compare()執(zhí)行環(huán)境的作用域鏈中則處于第二位。圖展示了包含上述關(guān)系的compare()函數(shù)執(zhí)行時(shí)的作用域鏈。
后臺(tái)的每個(gè)執(zhí)行環(huán)境都有一個(gè)表示變量的對(duì)象——變量對(duì)象。全局環(huán)境的變量對(duì)象始終存在,而像compare()函數(shù)這樣的局部環(huán)境的變量對(duì)象,則只在函數(shù)執(zhí)行的過(guò)程中存在。在創(chuàng)建compare()函數(shù)時(shí),會(huì)創(chuàng)建一個(gè)預(yù)先包含全局變童對(duì)象的作用域鏈,這個(gè)作用域鏈被保存在內(nèi)部的[[Scope]]屬性中。當(dāng)調(diào)用compare()函數(shù)時(shí),會(huì)為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境,然后通過(guò)復(fù)制函數(shù)的[[Scope]]屬性中的對(duì)象構(gòu)建起執(zhí)行環(huán)境的作用域鏈。此后,又有一個(gè)活動(dòng)對(duì)象(在此作為變量對(duì)象使用)被創(chuàng)建并被推入執(zhí)行環(huán)境作用域鏈的前端。對(duì)于這個(gè)例子中compare()函數(shù)的執(zhí)行環(huán)境而言,其作用域鏈中包含兩個(gè)變量對(duì)象:本地活動(dòng)對(duì)象和全局變量對(duì)象。顯然,作用域鏈本質(zhì)上是一個(gè)指向變量對(duì)象的指針列表,它只引用但不實(shí)際包含變量對(duì)象。
無(wú)論什么時(shí)候在函數(shù)中訪問一個(gè)變量時(shí),就會(huì)從作用域鏈中搜索具有相應(yīng)名字的變量。一般來(lái)講,當(dāng)函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷毀,內(nèi)存中僅保存全局作用域(全局執(zhí)行環(huán)境的變量對(duì)象)。 但是,閉包的情況又有所不同。
function createComparisonFunction(propertyName) { return function(object1, object2){ var valuel = objectl[propertyName]; var value2 = object2[propertyName]; if (valuel < value2){ return -1; } else if (valuel > value2){ return 1; } else { return 0; } };}
在另一個(gè)函數(shù)內(nèi)部定義的函數(shù)會(huì)將包含函數(shù)(即外部函數(shù))的活動(dòng)對(duì)象添加到它的作用域鏈中。因此,在createComparisonFunction()涵數(shù)內(nèi)部定義的匿名函數(shù)的作用域鏈中,實(shí)際上將會(huì)包含外部函數(shù)createComparisonFunction()的活動(dòng)對(duì)象。圖展示了當(dāng)下列代碼執(zhí)行時(shí),包含函數(shù)與內(nèi)部匿名函數(shù)的作用域鏈。
var compare = createComparisonFunction("name");var result = compare({ name: "Nicholas" }, { naine: BGreg" });
在匿名函數(shù)從createComparisonFunction()中被返冋后,它的作用域鏈被初始化為包含createComparisonFunction()函數(shù)的活動(dòng)對(duì)象和全局變量對(duì)象。這樣,匿名函數(shù)就可以訪問在createComparisonFunction()中定義的所有變量。更重要的是,createCoir.parisonFunction() 函數(shù)在執(zhí)行完畢后,其活動(dòng)對(duì)象也不會(huì)被銷毀,因?yàn)槟涿瘮?shù)的作用域鏈仍然在引用這個(gè)活動(dòng)對(duì)象。換句話說(shuō),當(dāng)createComparisonFunction()函數(shù)返回后,其執(zhí)行環(huán)境的作用域鏈會(huì)被銷毀,但它的活動(dòng)對(duì)象仍然會(huì)留在內(nèi)存中;直到匿名函數(shù)被銷毀后,createComparisonFunction()的活動(dòng)對(duì)象才會(huì)被銷毀,例如:
var compareNames = createComparisonFunction("name");//調(diào)用函數(shù)var result = compareNames({ name: "Nicholas" ), { name:"Greg" });//解除對(duì)匿名函數(shù)的引用(以便釋放內(nèi)存)compareNanies = null;
首先,創(chuàng)建的比較函數(shù)被保存在變量coinpareNames中。而通過(guò)將compareNames設(shè)置為等于null解除該函數(shù)的引用,就等于通知垃圾問收例程將其清除。隨著匿名函數(shù)的作用域鏈被銷毀,其他作用域 (除r全局作用域)也都可以安全地銷毀了。圖 展示了調(diào)用conpareNamesO的過(guò)程中產(chǎn)生的作用域鏈之間的關(guān)系。
---------------------------------------------------------------------------------------------------------------------------------------
閉包無(wú)處不在,弄懂它很重要。
完
轉(zhuǎn)載需注明轉(zhuǎn)載字樣,標(biāo)注原作者和原博文地址。
聯(lián)系客服