Javascript閉包真經(jīng)_第1頁
Javascript閉包真經(jīng)_第2頁
Javascript閉包真經(jīng)_第3頁
Javascript閉包真經(jīng)_第4頁
Javascript閉包真經(jīng)_第5頁
已閱讀5頁,還剩6頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、.Javascript閉包真經(jīng)繼前陣子寫完對象真經(jīng)后,這篇文章我嘗試盡力的去講透Js中的閉包。這里要感謝愛民,愛民的書寫得很好,我從中獲益良多。不過這次我打算換一種思路來寫這篇真經(jīng),就是采用提問答復(fù)的方式,我下面先提出我要答復(fù)的問題,假設(shè)讀者你都很自信的可以答復(fù)上,那么就可以考慮干別的事情去了。假設(shè)感覺自己有點把握不準(zhǔn)就請給我一步步的尋址吧。:我保證最后你就會豁然開朗,明白閉包的真諦。問題集:什么是函數(shù)實例?什么是函數(shù)引用?什么是閉包?閉包里有什么玩意?函數(shù)實例、函數(shù)引用和閉包有什么聯(lián)絡(luò)?閉包的產(chǎn)生的情形?閉包中的標(biāo)識符的優(yōu)先級是什么樣的?閉包帶來的可見性問題。什么是函數(shù)實例呢?其實在我們平常

2、書寫代碼的過程中,寫的函數(shù)就是一段文本,只是對于編譯型語言來說會把它編譯為確定的二進制代碼,并放到確定的內(nèi)存位置執(zhí)行,而對于Js這樣的解釋型語言,就會在程序運行的時候把這段文本翻譯為計算機能懂的話。那么這里函數(shù)代碼的文本其實就是這個函數(shù)的類,而Js引擎解釋后的內(nèi)存中的數(shù)據(jù)就是這個函數(shù)的實例了。所以函數(shù)的實例就是Js引擎讀過這段代碼后在內(nèi)存中產(chǎn)生的一段數(shù)據(jù)。什么是函數(shù)引用呢?函數(shù)引用就是指向剛剛所說的那段內(nèi)存數(shù)據(jù)的指針。也就是指向某個函數(shù)實例的指針。同一份實例可以有多個引用,只要該實例還有指向它的引用,占用的內(nèi)存就不會被釋放。什么是閉包呢?閉包就是函數(shù)實例執(zhí)行過程中動態(tài)產(chǎn)生的一塊新的內(nèi)存里的數(shù)據(jù)

3、集。這句話有可以表達兩層意思:最初閉包必須由函數(shù)實例被調(diào)用也就是函數(shù)實例執(zhí)行時才會由Js引擎動態(tài)生成;既然閉包也是一段內(nèi)存區(qū)域,當(dāng)沒有依賴于這個內(nèi)存中數(shù)據(jù)的引用時,才會被釋放,也就是閉包理論上才會被銷毀。記?。洪]包是執(zhí)行期的概念!那閉包里有什么玩意呢?這里我要引入Js引擎在語法分析期就構(gòu)造的兩類構(gòu)造。執(zhí)行上下文構(gòu)造Context和調(diào)用對象構(gòu)造CallObject。我們還是先看一幅圖片:從上面的圖片我們?nèi)菀卓闯?,這是描繪一個函數(shù)實例信息的圖。Context構(gòu)造包含了myFunc函數(shù)的類型FUNCTION、名稱myFunc、形式參數(shù)x,y,z和最關(guān)鍵的CallObject構(gòu)造的引用body為Cal

4、lObject。而CallObject構(gòu)造就是很詳細的記錄了這個函數(shù)的全部語法分析構(gòu)造:內(nèi)部聲明的變量表localDeclVars、內(nèi)部聲明的具名函數(shù)表localDeclFuncs以及除去以上內(nèi)容之外的其它所有代碼文本的字符串表示source。好,如今有了這個語法分析期就產(chǎn)生的CallObject構(gòu)造,就好分析閉包里面會有什么了。當(dāng)一個函數(shù)實例被調(diào)用時,實際上是從這個原型復(fù)制了一份CallObject構(gòu)造到閉包那塊空的內(nèi)存中去。同時會給里面的數(shù)據(jù)賦值。這里有兩個規(guī)那么,大家要記下:在函數(shù)實例開場執(zhí)行時,localDeclVars中的所有值將被置為undefined;在函數(shù)實例執(zhí)行完畢并退出時,

5、localDeclVars不被重置,這就是Js中函數(shù)可以在內(nèi)部保存數(shù)據(jù)的特性的原因;這個閉包中的CallObject構(gòu)造中的數(shù)據(jù)能保存多久取決于是否還有對其的引用存在,所以這里就引出了一個推論,閉包的生存周期不依賴于函數(shù)實例。那么對于全局對象呢?是什么樣的構(gòu)造呢?其實和上面的函數(shù)實例的根本一樣,唯一不同的是,沒有Context,因為它在最頂層了。全局對象里面也有一個CallObject,只是這個CallObject有點特殊,就是localDeclVars里面的數(shù)據(jù)只會初始化一次,且整個CallObject里的數(shù)據(jù)總不被銷毀。除了我講的復(fù)制了一份CallObject,再往里賦值,閉包其實還包括了

6、一個upvalue數(shù)組,這里數(shù)組里裝載的是閉包鏈上一級閉包中的標(biāo)識符localDeclVars和localDeclFuncs及它自身的upvalue數(shù)組的引用函數(shù)實例、函數(shù)引用和閉包有什么聯(lián)絡(luò)呢?要答復(fù)這樣的問題,就要好好分別理解上面說的這個三個分別指的是什么。然后我想在答復(fù)這個問題前,通過代碼來感知一些東西。其實有時候人的思維需要一個載體去依靠,也就是我們常說的理論才能出真知。javascriptfunction myFuncthis.doFunc=functionvar obj=;/進入myFunc,獲得doFunc的一個實例myFunc.callobj;/套取函數(shù)實例的一個引用并賦值給f

7、unc var func=obj.doFunc;/再次進入myFunc,又獲得了doFunc的一個新實例myFunc.callobj;/比較兩次獲得的函數(shù)實例,結(jié)果顯示false,說明是不同的實例printfunc=obj.doFunc;/顯示false/顯示true,說明兩個函數(shù)實例的代碼文本完全一樣printfunc.toString=obj.doFunc.toString;/顯示true/javascript我兩次調(diào)用了myFunc,卻發(fā)現(xiàn)對于一樣的代碼文本產(chǎn)生了不同的實例func和obj.doFunc。這是什么原因呢?事實上是我們每次調(diào)用myFunc函數(shù)時,Js引擎進入myFunc函數(shù)

8、,完成賦值操作時必然要解釋那段匿名函數(shù)代碼文本,所以以它為藍本產(chǎn)生了一個函數(shù)實例。此后我們讓obj對象的doFunc成員指向這個實例。屢次調(diào)用,就屢次的進入myFunc做賦值,就屢次的產(chǎn)生新的同樣的代碼文本的實例,所以兩次obj.doFunc成員所指的函數(shù)實例是不一樣的但函數(shù)實例的靜態(tài)代碼文本完全一樣,這里都是一個匿名空函數(shù)。再看一個實例與引用的例子:javascriptfunction MyObjectMyOtotype.method=function;var obj1=new MyObject;var obj2=new MyObject;printobj1.method=

9、obj2.method;/顯示true/javascript這里的obj1和obj2的方法其實也是對一個函數(shù)實例的引用,但怎么就一樣呢?其實這樣回憶我在Js對象真經(jīng)里說的,prototype原型是一個對象實例,里面維護了自己的成員表,而MyObject的對象實例會有一個指針指向這個成員表,而不是復(fù)制一份。所以obj1.method和obj2.method本質(zhì)都是執(zhí)行同一個函數(shù)實例的兩個引用。再來:javascriptvar OutFunc=function/閉包1 var MyFunc=function;return function/閉包2 return MyFunc;/注意這里的一個調(diào)用操

10、作var f1=OutFunc;var f2=OutFunc;printf1=f2;/顯示true/javascript這個例子其實玩了一個視覺陷阱。假設(shè)你沒有注意那個函數(shù)調(diào)用的號,就會由前面講的知識推導(dǎo)出f1和f2所指的實例不是同一個盡管它們代碼文本都一樣。但這個例子我想講的其實是關(guān)于upvalue數(shù)組的問題,不會就忘記這個數(shù)組了吧。這里其實OutFunc最終成了一個匿名函數(shù)functionreturn MyFunc實例的引用,而由于閉包1內(nèi)的localDeclFuncs里的數(shù)據(jù)有被引用,所以閉包1不會被銷毀。然后調(diào)用這個OutFunc函數(shù)實例返回的是MyFunc函數(shù)的實例,但僅有一個,也就

11、是屢次調(diào)用都返回同一個,其原因在于匿名函數(shù)functionreturn MyFunc實例其實是通過它運行時產(chǎn)生的閉包2中的upvalue中來找到MyFunc的位于閉包1中,而MyFunc在我的那個要你們特別留意的操作時就產(chǎn)生了,在閉包1的localDeclVars中記錄下來。前面的函數(shù)實例都對應(yīng)的是一個閉包,這里我想拿出一個函數(shù)實例可以對應(yīng)多個閉包的例子來為等下答復(fù)函數(shù)實例、函數(shù)引用和閉包有什么聯(lián)絡(luò)做鋪墊。javascriptvar globalFunc;function myFunc/閉包1 ifglobalFuncglobalFunc;print'do myFunc:'+s

12、tr;var str='test';if!globalFuncglobalFunc=function/閉包2 print'do globalFunc:'+str;return arguments.callee;myFunc;/*輸出結(jié)果:do myFunc:undefined/第一次執(zhí)行時顯示do globalFunc:test/第二次執(zhí)行時顯示do myFunc:undefined/第二次執(zhí)行時顯示*/javascript這個例子有點復(fù)雜,請讀者耐著性質(zhì)冷靜的分析下。這里的myFunc函數(shù)實例被調(diào)用了兩次,但都是同一個myFunc的實例。這是因為第一次調(diào)用my

13、Func實例后返回了一個它的引用通過return arguments.callee。然后立即被操作,完成第二次調(diào)用。在第一次調(diào)用中,str還沒有并賦值,但已經(jīng)被聲明了并被初始化為了undefined,所以顯示do myFunc:undefined。而接著后面的語句,實現(xiàn)了賦值,這時候str的值為"test",由于globalFunc為undefined,所以賦值為一個匿名函數(shù)實例的引用。由于globalFunc是全局變量,屬于不會銷毀的全局閉包。所以這時候在第一次myFunc函數(shù)實例調(diào)用完畢后,全局閉包中的globalFunc所指的匿名函數(shù)實例引用了閉包1中的str值為te

14、st,導(dǎo)致閉包1不會被銷毀,等第二次myFunc函數(shù)實例被調(diào)用時,產(chǎn)生了新的閉包3,同時也會先聲明變量str,初始化為undefined。由于全局變量globalFunc有值,所以會直接調(diào)用對應(yīng)的閉包1中的那個匿名函數(shù)實例,產(chǎn)生了閉包2,同時閉包2通過upvalue數(shù)組獲得了閉包1中的str,閉包1中當(dāng)時為"test",所以輸出了do globalFunc:test。注意,這里就表達了同一個實例屢次調(diào)用都要產(chǎn)生新的閉包,且閉包中的數(shù)據(jù)不是共享的。這個例子總的來說反響了幾個問題:Js中同一個實例可能擁有多個閉包;Js中函數(shù)實例與閉包的生存周期是分別管理的;Js中函數(shù)實例被調(diào)用

15、,總是會產(chǎn)生一個新的閉包,但上次調(diào)用產(chǎn)生的閉包是否已經(jīng)銷毀取決于那個閉包中是否有被其他閉包引用的數(shù)據(jù)。講到這里,是時候揭曉謎團了。函數(shù)實例可以有多個函數(shù)引用,而只要存在函數(shù)實例的引用,該實例就不會被銷毀。而閉包是函數(shù)實例被調(diào)用時產(chǎn)生的,但不一定隨著調(diào)用完畢就銷毀,一個函數(shù)實例可以同時擁有多個閉包。閉包有些什么產(chǎn)生的情形呢?其實細分可以分為:全局閉包;具名函數(shù)實例產(chǎn)生的閉包;匿名函數(shù)實例產(chǎn)生的閉包;通過new Functionbodystr產(chǎn)生的函數(shù)實例的閉包;通過with語句所指示的對象的閉包。關(guān)于后面3種大家先不要急,我會在后面的閉包帶來的可見性問題中舉例說明。閉包中的標(biāo)識符的優(yōu)先級是什么樣

16、的呢?要理解優(yōu)先級,就要先說說閉包中有什么標(biāo)識符。完好地說,函數(shù)實例閉包內(nèi)的標(biāo)識符系統(tǒng)包括:thislocalDeclVars函數(shù)實例的形式參數(shù)argumentslocalDeclFuncs我們先看一個例子:javascriptfunction foo1argumentsprinttypeof arguments;foo11000;/顯示number function argumentsprinttypeof arguments;arguments;/顯示object function foo2foo2printfoo2;foo2'hi';/顯示hi/javascript從上面

17、我們不難分析出,形式參數(shù)優(yōu)先于arguments由foo1知,內(nèi)置對象arguments優(yōu)先于函數(shù)名由arguments知,最后一個反響了形式參數(shù)優(yōu)于函數(shù)名。其實有前面兩個也說明了這點形式參數(shù)優(yōu)于arguments優(yōu)于函數(shù)名。再看一個例子:javascriptfunction foostrvar str;printstr;foo'test';/顯示test function foo2strvar str='not test';printstr;foo2'test';/顯示not test/javascript這個例子可以得出一個結(jié)論:當(dāng)形式參數(shù)名

18、與未賦值的部分變量名重復(fù)時,取形式參數(shù);當(dāng)形式參數(shù)名與有值的部分變量名重復(fù)時,取部分變量值。而this關(guān)鍵字,我們不能用它去做函數(shù)名,也不能作為形式參數(shù),所以沒有沖突,可以理解為它是優(yōu)先級最高的。閉包帶來的可見性問題,這不是一個提問,而是一個對于閉包理解的理論。之所以這樣說,是因為其實我們遇到的很多標(biāo)識符可見性的問題,其實和閉包息息相關(guān)。比方內(nèi)部函數(shù)可以訪問外部函數(shù)中的標(biāo)識符,完全是由于內(nèi)部函數(shù)實例的閉包通過其upvalue數(shù)組來獲得外部函數(shù)實例閉包中的數(shù)據(jù)的??梢娦愿采w的本質(zhì)就是在內(nèi)部函數(shù)實例閉包中能找到對應(yīng)的標(biāo)識符,就不會去通過upvalue數(shù)組尋找上一級閉包里的標(biāo)識符了。還有比方我們假設(shè)

19、在一個函數(shù)里面不用var關(guān)鍵字來聲明一個標(biāo)識,就會隱式的在全局聲明一個這樣的標(biāo)識。其實這就是因為在函數(shù)實例的所有閉包中找不到對應(yīng)的標(biāo)識,一直到了全局閉包中,也沒有,所以Js引擎就在全局閉包這個閉包有點特殊聲明了一個這樣的標(biāo)識來作為對于你的代碼的一種容錯,所以最好不要這樣去用,容易污染全局閉包的標(biāo)識符系統(tǒng),要知道全局閉包是不會銷毀的。這里我想補充講一個很重要的話題,就是閉包在閉包鏈中的位置怎么確定?全局閉包沒什么好說的,必然是最頂層的;對于具名函數(shù)實例產(chǎn)生的閉包,其實由該具名函數(shù)實例的靜態(tài)語法作用域決定,也就是Js引擎做語法分析時,它處于什么語法作用域,那以后產(chǎn)生的閉包,在閉包鏈中也要按這個順序

20、,因為函數(shù)實例被執(zhí)行時也會在這個位置去執(zhí)行。也就是假設(shè)它在代碼文本里表現(xiàn)為一個函數(shù)里的函數(shù),那將來它的閉包就被外面函數(shù)實例產(chǎn)生的閉包包裹;注意:對于SpiderMonkey有點不同,在with語句里面的函數(shù)閉包鏈隸屬于with語句翻開的閉包。這在后面會特別舉例。匿名函數(shù)實例的閉包位置由匿名函數(shù)直接量的創(chuàng)立位置決定,動態(tài)的參加閉包鏈中,而與它是否執(zhí)行過無關(guān),因為將來要產(chǎn)生閉包時,匿名函數(shù)直接量還是會回到創(chuàng)立位置執(zhí)行;通過new Functionbodystr產(chǎn)生的函數(shù)實例的閉包很有意思,它不管在哪里創(chuàng)立,總是直接緊緊的隸屬于全局閉包,也就是其upvalue數(shù)組里的數(shù)據(jù)是全局閉包中的數(shù)據(jù);通過wi

21、th語句所指示的對象的閉包位置由該with語句執(zhí)行時的詳細位置決定,動態(tài)的參加閉包鏈中。我們來看一些例子,用來說明上面的結(jié)論。javascriptvar value='global value';function myFuncvar value='local value';var foo=new Function'printvalue';foo;myFunc;/顯示global value var obj=;var events=m1:'clicked',m2:'changed';fore in eventsobj

22、e=functionprinteventse;obj.m1;/顯示一樣obj.m2;/顯示一樣var obj=;var events=m1:'clicked',m2:'changed';fore in eventsobje=new Function'printevents"'+e+'"';obj.m1;/顯示clicked obj.m2;/顯示changed/javascript上面一大段代碼顯然可以分為3部分。第一部分說明了通過new Functionbodystr產(chǎn)生的函數(shù)實例的閉包在閉包鏈中的位置總是直接

23、隸屬于全局閉包,而不是myFunc的閉包。第二段顯示一樣的原因在于調(diào)用m1、m2時,它們各自的閉包都要引用全局閉包中的e,這時e已經(jīng)是events數(shù)組for迭代中的最后一個元素的索引了。第三段只用了new Function代替了原來的function,就發(fā)生了變化,那是因為Function構(gòu)造器傳入的都是字符串,不會將來引用全局閉包的e了。假設(shè)我們把閉包的可見性理解為閉包的upvalue數(shù)組和閉包內(nèi)的標(biāo)識符系統(tǒng),那一般函數(shù)實例的閉包和通過with語句所指示的對象的閉包在前者上是一致的,而在后者上處理就不一樣了。原因在于:通過with語句所指示的對象的閉包只有對象成員名可訪問,而沒有this、函

24、數(shù)形式參數(shù)、自動構(gòu)建賦值的內(nèi)置對象arguments以及l(fā)ocalDeclFuncs,而對于該閉包中的var聲明變量的效果,就詳細的引擎實現(xiàn)會有些差異,我覺得應(yīng)該防止使用這種代碼的寫法,所以這里就不詳細討論了。看一些與with語句有關(guān)的代碼:javascriptvar x;withx=x1:'x1',x2:'x2'x.x3='x3';/通過全局變量x訪問匿名對象forvar iin xprintxi;function selfxreturn x.self=x;withselfx1:'x1',x2:'x2'self.

25、x3='x3';/通過匿名對象自身的成員self訪問對象自身forvar iin selfifi!='self'printselfi;/javascript上面的代碼里注釋都寫得很明白了,我就不多說了,我們?nèi)缃駚砜匆粋€嚴重的問題:javascriptvar obj=v:10;var v=1000;withobjfunction foov*=3;foo;/*withobjobj.foo=functionv*=3;obj.foo;alertobj.v;/顯示30 alertv;/顯示1000*/alertobj.v;/顯示10 alertv;/顯示3000/java

26、script這段代碼在ie8、safari3.2、opera9和chrome 8中都表示為10 3000,但在Firefox3.0.7中就變?yōu)榱?0 1000,這個就很特別了,也許是bug問題。這里就違犯了我前面說的具名函數(shù)實例閉包在閉包鏈中位置的一般情況,即語法分析期就決定了,視with語句于透明。其實從代碼的第一印象上說,似乎就是給obj對象的屬性v乘了3,而不是全局變量v。其實在除了Firefox3.0.7其他版本的FF沒有測試過,foo函數(shù)的閉包位置在語法分析期就決定了,直接隸屬于全局閉包,而with語句翻開的obj對象的閉包執(zhí)行的位置在決定了它也直接隸屬于全局閉包,所以就出現(xiàn)了并列的情況,那么foo函數(shù)里面的乘3自然就無法訪問with閉包里的對象obj.v了,所以顯示為10 3000。假設(shè)想顯示為30和1000我們完全可以利用匿名函數(shù)實例閉

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論