終于講到閉包了,當(dāng)你在百度上搜索閉包時(shí),你會(huì)被搜索出來(lái)的結(jié)果嚇一跳,我的天,為什么說(shuō)得都不一樣?直到把所有的解釋都看過(guò)了,我就只想說(shuō)一句,到底誰(shuí)說(shuō)的是對(duì)的…
在這么多的不同解釋里,我認(rèn)真思考了很久,到底該相信誰(shuí)?最后我選擇相信大道至簡(jiǎn),因?yàn)槲沂冀K覺(jué)得理論來(lái)源于實(shí)踐,而實(shí)踐一定不是在象牙塔里,而是可以摸得到的簡(jiǎn)單的東西。
下面就來(lái)講最原始的閉包的示例:
function fn(){var max=10;return function bar(x){if(x>max){alert(x);}}}var f1=fn();f1(15);//15
如上代碼,bar函數(shù)作為返回值,賦值給f1變量。執(zhí)行f1(15)時(shí),就相當(dāng)于執(zhí)行bar(15),沿著作用域鏈取到fn作用域下的max變量的值。
這已經(jīng)產(chǎn)生了一個(gè)最基本的閉包,用自然語(yǔ)言描述的話就是:
(1)定義普通函數(shù) A
(2)在 A 中定義普通函數(shù) B
(3)在 A 中返回 B
(4)執(zhí)行 A, 并把 A 的返回結(jié)果賦值給變量 C
(5)執(zhí)行 C
而它的形式就是用“return”作為橋梁,鏈接A外的變量C和A內(nèi)的變量B。
用一個(gè)歸納的話說(shuō)就是:
當(dāng)一個(gè)內(nèi)部函數(shù)被其外部函數(shù)之外的變量引用時(shí),就形成了一個(gè)閉包
如果你認(rèn)為這就是閉包的全部,那你就有些狹隘了。
下面一個(gè)例子,就展示了這個(gè)狹隘之處:
function A(){ var count=0; function B(){ count++; alert(count); } return B;} var c=A(); c();//1 c();//2 c();//3
那么你就會(huì)驚奇于閉包還有如此神秘的一面,其實(shí)對(duì)于這一面,網(wǎng)上有很多解釋?zhuān)忉尩梦寤ò碎T(mén),但大都沒(méi)解釋到本質(zhì)上。
其實(shí)這一系列的文章的標(biāo)題都是“javascript執(zhí)行上下文、作用域與閉包”,說(shuō)明閉包與執(zhí)行上下文,作用域是有緊密聯(lián)系的,下面就來(lái)演示一下閉包那神秘的一面的本質(zhì)。
咱們可以拿本文的第一段代碼(稍作修改)來(lái)分析一下。
function fn(){var max=10;return function bar(x){if(x>max){alert(x);}}}var f1=fn(),max=100;f1(15);//15
我們來(lái)一步一步揭開(kāi)那神秘的面紗:
在這之前,告訴大家一個(gè)很重要的東西—-我們?cè)谇懊嬷v執(zhí)行上下文棧時(shí)當(dāng)一個(gè)函數(shù)被調(diào)用完成之后,其執(zhí)行上下文將被被彈出,即銷(xiāo)毀,其中的變量也會(huì)被同時(shí)銷(xiāo)毀。
但我們要銘記:在閉包里,函數(shù)調(diào)用完成之后,其執(zhí)行上下文環(huán)境不會(huì)立即被銷(xiāo)毀。(這句話是不正確的,但這里讀者可以先用這個(gè)錯(cuò)誤的解釋去看下面的步驟,先對(duì)閉包形成的原因有一個(gè)大致的了解,最終正確的理解在 厚積薄發(fā)—從此再也不用擔(dān)心閉包問(wèn)題)
第一步,代碼執(zhí)行前生成全局上下文環(huán)境,并在執(zhí)行時(shí)對(duì)其中的變量進(jìn)行賦值。此時(shí)全局上下文環(huán)境是活動(dòng)狀態(tài):
第二步,執(zhí)行第17行代碼時(shí),調(diào)用fn(),產(chǎn)生fn()執(zhí)行上下文環(huán)境,壓棧,并設(shè)置為活動(dòng)狀態(tài)。
第三步,執(zhí)行完第17行,fn()調(diào)用完成。按理說(shuō)應(yīng)該銷(xiāo)毀掉fn()的執(zhí)行上下文環(huán)境,但是因?yàn)閳?zhí)行fn()時(shí),返回的是一個(gè)函數(shù)。函數(shù)的特別之處在于可以創(chuàng)建一個(gè)獨(dú)立的執(zhí)行上下文,當(dāng)代碼執(zhí)行到return時(shí),按理說(shuō)應(yīng)該把fn()的執(zhí)行上下文銷(xiāo)毀,但是由于return的bar()存在對(duì)fn()上下文的引用,(因?yàn)榇嬖趯?duì)變量max的需要)所以不能將其銷(xiāo)毀,這里就要提到j(luò)s的垃圾回收機(jī)制,當(dāng)一個(gè)函數(shù)不存在外部對(duì)它的引用的時(shí)候,就自動(dòng)將其銷(xiāo)毀。這里存在對(duì)把不能把fn()上下文銷(xiāo)毀。
第四步,執(zhí)行到第18行時(shí),全局上下文環(huán)境將變?yōu)榛顒?dòng)狀態(tài),但是fn()上下文環(huán)境依然會(huì)在執(zhí)行上下文棧中。另外,執(zhí)行完第18行,全局上下文環(huán)境中的max被賦值為100。如下圖:
第五步,執(zhí)行到第20行,執(zhí)行f1(15),即執(zhí)行bar(15),創(chuàng)建bar(15)上下文環(huán)境,并將其設(shè)置為活動(dòng)狀態(tài)
執(zhí)行bar(15)時(shí),max是自由變量,需要向創(chuàng)建bar函數(shù)的作用域中查找,找到了max的值為10,
這里的重點(diǎn)就在于,創(chuàng)建bar函數(shù)是在執(zhí)行fn()時(shí)創(chuàng)建的。fn()早就執(zhí)行結(jié)束了,但是fn()執(zhí)行上下文環(huán)境還存在與棧中,因此bar(15)時(shí),max可以查找到。如果fn()上下文環(huán)境銷(xiāo)毀了,那么max就找不到了。
可以看到,使用閉包會(huì)使變量保存在內(nèi)存中,但是缺點(diǎn)就是會(huì)增加內(nèi)存開(kāi)銷(xiāo)。
在網(wǎng)上有些資料解釋閉包的主要用途有兩個(gè),一個(gè)是可以使外部變量訪問(wèn)到一個(gè)函數(shù)的內(nèi)部變量,一個(gè)是使變量保存在內(nèi)存中。其實(shí)這句話不太貼切,正確的說(shuō)法應(yīng)該是:使外部變量訪問(wèn)到一個(gè)函數(shù)的內(nèi)部變量是閉包的形式,使變量保存在內(nèi)存中是閉包的工作原理。
到這里,如果覺(jué)得有種恍然大悟的感覺(jué),不妨試一試下面的例子
function A(){ var count=0; function B(){ count++; alert(count); } return B;} var c=A(); c();//1 c();//2 c();//3
大家可以試一試解釋為什么三次調(diào)用c(),結(jié)果分別是1,2,3呢?
如果還有些不懂,我會(huì)在下一篇里詳細(xì)講一下我的理解。
本文參考了王福朋老師的深入理解javascript原型和閉包(15)——閉包
聯(lián)系客服