詳解JavaScript中的閉包是如何產(chǎn)生的
這次從內(nèi)存管理的角度來(lái)看看,閉包是怎么產(chǎn)生的。
我們知道,在調(diào)用函數(shù)時(shí),其實(shí)會(huì)產(chǎn)生臨時(shí)的 調(diào)用棧。這些調(diào)用棧保存的是 執(zhí)行上下本,并實(shí)際保存在 棧內(nèi)存 中。
每執(zhí)行一個(gè)函數(shù),函數(shù)內(nèi)的局部臨時(shí)變量會(huì)臨時(shí)保存起來(lái)。如果此時(shí)函數(shù)又調(diào)用了另一個(gè)函數(shù),另一個(gè)函數(shù)下的局部變量也要保存下來(lái),就這樣,我們產(chǎn)生了棧。
當(dāng)一個(gè)函數(shù)執(zhí)行完后,它對(duì)應(yīng)的局部臨時(shí)變量就會(huì)被銷毀。
局部變量保存下來(lái),是為了保護(hù)上下文現(xiàn)場(chǎng)。
舉例說(shuō)明一下:
function a() { const a_num = 99; const a_obj = { val: "a" }; b(); } function b() { const b_str = "text"; c(); } function c() { const c_bool = true; // debugger } a();
這里我們嵌套調(diào)用了 a、b、c 函數(shù),會(huì)產(chǎn)生如下的調(diào)用棧。
基本類型的臨時(shí)變量,會(huì)直接保存到棧內(nèi)存中,對(duì)于引用類型,則是在堆內(nèi)存中生成,然后將地址拿到,保存到棧內(nèi)存中。
引用類型為什么不直接放到棧內(nèi)存中?因?yàn)闂?nèi)存不是很大,很容易就棧溢出,而引用類型通常很大。
閉包的產(chǎn)生
函數(shù)調(diào)用完成后,它內(nèi)部聲明的臨時(shí)變量會(huì)被銷毀。理論上應(yīng)該如此,但如果使用了閉包,可以會(huì)讓臨時(shí)變量一直保留不被銷毀。
例子:
function createCounter() { let count = 0; let otherVal = "other val"; return function counter() { // debugger; console.log(count++); }; } const counter = createCounter(); console.log(counter());
執(zhí)行過(guò)程為:
- 執(zhí)行函數(shù) createCounter 時(shí),會(huì)創(chuàng)建一個(gè)空的上下文對(duì)象。
- 遇到內(nèi)部函數(shù) counter,會(huì) 預(yù)掃描內(nèi)部函數(shù) counter 使用了 createCounter 下的哪些便利,最終掃描出 count 變量。于是在堆內(nèi)存創(chuàng)建一個(gè)閉包
Closure (createCounter)
對(duì)象,將 count 加進(jìn)去。otherVal 不會(huì)加到閉包對(duì)象上,因?yàn)樗鼪](méi)有被使用。 - 這個(gè)內(nèi)部函數(shù)最后被返回,被引用,閉包就一直不會(huì)銷毀。
使用 DevTool 可以觀察到這個(gè)閉包對(duì)象:
所以,如果一個(gè)閉包返回的函數(shù)執(zhí)行完后不用了,要設(shè)置為 null。否則它關(guān)聯(lián)的閉包對(duì)象會(huì)一直在那里占用內(nèi)存。
多個(gè)內(nèi)部函數(shù)共享一個(gè)閉包對(duì)象
另外,如果有多個(gè)內(nèi)部函數(shù),這些函數(shù)會(huì)共用同一個(gè)閉包對(duì)象。即使其中的一個(gè)內(nèi)部函數(shù)不會(huì)返回,它也會(huì)給閉包對(duì)象加?xùn)|西。
下面我們加了一個(gè) printOtherVal 的內(nèi)部函數(shù),它并不返回,但還是會(huì)導(dǎo)致返回 counter 函數(shù)對(duì)應(yīng)的閉包對(duì)象帶上了它不需要的 otherVal 變量。
這是 JS 引擎處理閉包策略問(wèn)題,理論不應(yīng)該有這樣奇怪的效果。
結(jié)尾
調(diào)用函數(shù)時(shí),會(huì)產(chǎn)生調(diào)用棧,將當(dāng)前函數(shù)上下文入棧,會(huì)保存基本類型變量。引用變量會(huì)在堆內(nèi)存中創(chuàng)建,然后在棧內(nèi)存中引用過(guò)來(lái)。
因?yàn)?JavaScript 中函數(shù)是第一公民,所以會(huì)有閉包的概念。當(dāng)發(fā)現(xiàn)內(nèi)部函數(shù),會(huì)創(chuàng)建一個(gè)閉包對(duì)象,將其中使用到的外部函數(shù)變量保存到該閉包對(duì)象下。之后內(nèi)部函數(shù)被調(diào)用時(shí),就會(huì)從閉包里提取變量,如果找不到則從全局上下文提取。
到此這篇關(guān)于詳解JavaScript中的閉包是如何產(chǎn)生的的文章就介紹到這了,更多相關(guān)JavaScript閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談layui框架自帶分頁(yè)和表格重載的接口解析問(wèn)題
今天小編就為大家分享一篇淺談layui框架自帶分頁(yè)和表格重載的接口解析問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09JS控制div跳轉(zhuǎn)到指定的位置的幾種解決方案總結(jié)
這篇文章主要介紹了JS控制div跳轉(zhuǎn)到指定的位置的幾種解決方案總結(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。2016-11-11javascript入門之window對(duì)象【新手必看】
本文系統(tǒng)介紹了javascript的window對(duì)象以及一些控制函數(shù)的用法,僅供大家參考2016-11-11JavaScript實(shí)現(xiàn)九宮格移動(dòng)拼圖游戲
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)九宮格移動(dòng)拼圖游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Bootstrap實(shí)現(xiàn)模態(tài)框效果
這篇文章主要為大家詳細(xì)介紹了Bootstrap實(shí)現(xiàn)模態(tài)框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09JS調(diào)用打印方法設(shè)置頁(yè)眉頁(yè)腳的實(shí)例
一個(gè)網(wǎng)頁(yè)打印相關(guān)功能的擴(kuò)展演示特效,在實(shí)現(xiàn)了打印功能外,還實(shí)現(xiàn)了打印預(yù)覽、打印前的頁(yè)眉頁(yè)腳設(shè)置,直接打印等功能,以前對(duì)JS打印前設(shè)置頁(yè)腳見(jiàn)的不多,所以這一個(gè)也算是挺有價(jià)值的,希望閑暇時(shí)參閱2013-05-05