一,、什么是閉包?
“官方”的解釋是:閉包是一個(gè)擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個(gè)函數(shù)),,因而這些變量也是該表達(dá)式的一部分,。 相信很少有人能直接看懂這句話,因?yàn)樗枋龅奶珜W(xué)術(shù),。其實(shí)這句話通俗的來說就是:JavaScript中所有的function都是一個(gè)閉包,。不過一般來說,嵌套的function所產(chǎn)生的閉包更為強(qiáng)大,,也是大部分時(shí)候我們所謂的“閉包”,。看下面這段代碼: function a() { //a是全局函數(shù),它的作用域是window,作用域鏈也是window. var i = 0; function b() { //b的作用域是a,作用域鏈?zhǔn)?->a-->window,如:引用b: window.a()(); alert(++i); } return b; } var c = a(); var d = a();
c(); //1 c();//2
d();//1
這段代碼有兩個(gè)特點(diǎn): 函數(shù)b嵌套在函數(shù)a內(nèi)部; 函數(shù)a返回函數(shù)b,。 這樣在執(zhí)行完var c=a()后,,變量c實(shí)際上是指向了函數(shù)b,b中用到了變量i,,再執(zhí)行c()后就會(huì)彈出一個(gè)窗口顯示i的值(第一次為1),。這段代碼其實(shí)就創(chuàng)建了一個(gè)閉包,為什么,?因?yàn)楹瘮?shù)a外的變量c引用了函數(shù)a內(nèi)的函數(shù)b,,就是說: 當(dāng)函數(shù)a的內(nèi)部函數(shù)b被函數(shù)a外的一個(gè)變量引用的時(shí)候,就創(chuàng)建了一個(gè)我們通常所謂的“閉包”,。 讓我們說的更透徹一些,。所謂“閉包”,就是在構(gòu)造函數(shù)體內(nèi)定義另外的函數(shù)作為目標(biāo)對(duì)象的方法函數(shù),,而這個(gè)對(duì)象的方法函數(shù)反過來引用外層外層函數(shù)體中的臨時(shí)變量,。這使得只要目標(biāo) 對(duì)象在生存期內(nèi)始終能保持其方法,,就能間接保持原構(gòu)造函數(shù)體當(dāng)時(shí)用到的臨時(shí)變量值,。盡管最開始的構(gòu)造函數(shù)調(diào)用已經(jīng)結(jié)束,臨時(shí)變量的名稱也都消失了,,但在目 標(biāo)對(duì)象的方法內(nèi)卻始終能引用到該變量的值,,而且該值只能通這種方法來訪問。即使再次調(diào)用相同的構(gòu)造函數(shù),,但只會(huì)生成新對(duì)象和方法,,新的臨時(shí)變量只是對(duì)應(yīng)新 的值,和上次那次調(diào)用的是各自獨(dú)立的,。 為了更深刻的理解閉包,,下面讓我們繼續(xù)探索閉包的作用和效果。 二,、閉包有什么作用和效果,? 簡(jiǎn)而言之,閉包的作用就是在a執(zhí)行完并返回后,,閉包使得Javascript的垃圾回收機(jī)制GC不會(huì)收回a所占用的資源,,因?yàn)閍的內(nèi)部函數(shù)b的執(zhí)行需要依賴a中的變量。這是對(duì)閉包作用的非常直白的描述,,不專業(yè)也不嚴(yán)謹(jǐn),,但大概意思就是這樣,理解閉包需要循序漸進(jìn)的過程,。 在上面的例子中,,由于閉包的存在使得函數(shù)a返回后,a中的i始終存在,這樣每次執(zhí)行c(),,i都是自加1后alert出i的值,。 那么我們來想象另一種情況,如果a返回的不是函數(shù)b,,情況就完全不同了,。因?yàn)閍執(zhí)行完后,b沒有被返回給a的外界,,只是被a所引用,,而此時(shí)a也只會(huì)被b引用,因此函數(shù)a和b互相引用但又不被外界打擾(被外界引用),,函數(shù)a和b就會(huì)被GC回收,。(關(guān)于Javascript的垃圾回收機(jī)制將在后面詳細(xì)介紹) 三、閉包的微觀世界 如果要更加深入的了解閉包以及函數(shù)a和嵌套函數(shù)b的關(guān)系,,我們需要引入另外幾個(gè)概念:函數(shù)的執(zhí)行環(huán)境(excution context),、活動(dòng)對(duì)象(call object)、作用域(scope),、作用域鏈(scope chain),。以函數(shù)a從定義到執(zhí)行的過程為例闡述這幾個(gè)概念。 1. 當(dāng)定義函數(shù)a的時(shí)候,,js解釋器會(huì)將函數(shù)a的作用域鏈(scope chain)設(shè)置為定義a時(shí)a所在的“環(huán)境”,,如果a是一個(gè)全局函數(shù),則scope chain中只有window對(duì)象,。 2.當(dāng)執(zhí)行函數(shù)a的時(shí)候,,a會(huì)進(jìn)入相應(yīng)的執(zhí)行環(huán)境(excution context)。 在創(chuàng)建執(zhí)行環(huán)境的過程中,,首先會(huì)為a添加一個(gè)scope屬性,,即a的作用域,其值就為第1步中的scope chain,。即a.scope=a的作用域鏈=window,。 然后執(zhí)行環(huán)境會(huì)創(chuàng)建一個(gè)活動(dòng)對(duì)象(call object)?;顒?dòng)對(duì)象也是一個(gè)擁有屬性的對(duì)象,,但它不具有原型而且不能通過JavaScript代碼直接訪問。創(chuàng)建完活動(dòng)對(duì)象后,,把活動(dòng)對(duì)象添加到a的作用域鏈的最頂端,。 此時(shí)a的作用域鏈包含了兩個(gè)對(duì)象:a的活動(dòng)對(duì)象和window對(duì)象。
3.在活動(dòng)對(duì)象上添加一個(gè)arguments屬性,,它保存著調(diào)用函數(shù)a時(shí)所傳遞的參數(shù),。 最后把所有函數(shù)a的形參和內(nèi)部的函數(shù)b的引用也添加到a的活動(dòng)對(duì)象上,。在這一步中,完成了函數(shù)b的的定義,,因此如同第1步,,函數(shù)b的作用域鏈被設(shè)置為b所被定義的環(huán)境,即a的作用域,。 此時(shí)a的活動(dòng)對(duì)象包括:非原形屬性,,參數(shù),b的引用,。
4.到此,,整個(gè)函數(shù)a從定義到執(zhí)行的步驟就完成了。 此時(shí)a返回函數(shù)b的引用給c(全局),,又函數(shù)b的作用域鏈包含了對(duì)函數(shù)a的活動(dòng)對(duì)象的引用,,也就是說b可以訪問到a中定義的所有變量和函數(shù)。函數(shù)b被c引用,,函數(shù)b又依賴函數(shù)a,,因此函數(shù)a在返回后不會(huì)被GC回收。
四,、閉包的應(yīng)用場(chǎng)景 保護(hù)函數(shù)內(nèi)的變量安全,。以最開始的例子為例,函數(shù)a中i只有函數(shù)b才能訪問,,而無法通過其他途徑訪問到,,因此保護(hù)了i的安全性。 在內(nèi)存中維持一個(gè)變量,。依然如前例,由于閉包,,函數(shù)a中i的一直存在于內(nèi)存中,,因此每次執(zhí)行c(),都會(huì)給i自加1,。 通過保護(hù)變量的安全實(shí)現(xiàn)JS私有屬性和私有方法(不能被外部訪問)推薦閱讀:http://javascript./private.html 私有屬性和方法在Constructor外是無法被訪問的 function Constructor(...) { var that = this; var membername = value; function membername(...) {...} } 以上3點(diǎn)是閉包最基本的應(yīng)用場(chǎng)景,,很多經(jīng)典案例都源于此。 |
|