JavaScript函數(shù)執(zhí)行、作用域鏈以及內(nèi)存管理詳解
前言
在我們平常編寫JavaScript代碼的時(shí)候,難免會(huì)用到函數(shù),函數(shù)里面會(huì)有各種變量,這些變量的作用的范圍,以及在使用內(nèi)存存儲(chǔ)這些變量時(shí),內(nèi)存管理的問題,在平時(shí)編程亦或者面試時(shí),多多少少都會(huì)遇到,所以這篇文章針對這三個(gè)問題,進(jìn)行了深入的探討。
函數(shù)執(zhí)行
首先說一下JavaScript執(zhí)行代碼的順序,JavaScript在執(zhí)行一段可執(zhí)行代碼的時(shí)候,會(huì)創(chuàng)建一個(gè)執(zhí)行上下文棧(Execution Context Stack 簡稱ECStack),執(zhí)行全局代碼時(shí)創(chuàng)建的全局執(zhí)行上下文(Global Execution Context 簡稱GEC),以及執(zhí)行函數(shù)時(shí)創(chuàng)建的函數(shù)執(zhí)行上下文(Function Execution Context 簡稱FEC),在運(yùn)行時(shí)都會(huì)按順序放入棧中,而不管是全局執(zhí)行上下文還是函數(shù)執(zhí)行上下文在創(chuàng)建時(shí)都會(huì)有一個(gè)變量對象(variable Object)。
全局執(zhí)行上下文
在JavaScript執(zhí)行全局代碼時(shí),會(huì)創(chuàng)建一個(gè)全局執(zhí)行上下文,放入執(zhí)行上下文棧,還有一個(gè)GlobalObject(GO),全局執(zhí)行上下文中會(huì)有一個(gè)變量對象(variable Object),指向GO。
在編譯階段,GO會(huì)對在全局定義的變量初始化為undefined,當(dāng)遇到函數(shù)時(shí),便會(huì)以函數(shù)名作為GO的一個(gè)屬性名,值為存儲(chǔ)這個(gè)函數(shù)空間的內(nèi)存地址,在這個(gè)函數(shù)空間中,會(huì)有函數(shù)的執(zhí)行體(代碼段),還會(huì)存儲(chǔ)這個(gè)函數(shù)父級(jí)作用域。
編譯完成后,代碼開始執(zhí)行,便會(huì)對這些變量賦值,當(dāng)然里面除了這些,還有一些全局的對象和函數(shù),比如setTimeout,Date,String等等,還有一個(gè)屬性window賦值為this,當(dāng)遇到函數(shù)時(shí),便會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文放入執(zhí)行上下文棧中。
函數(shù)執(zhí)行上下文
上文說到,代碼執(zhí)行時(shí)候,執(zhí)行到函數(shù)時(shí),會(huì)創(chuàng)建函數(shù)執(zhí)行上下文,并且函數(shù)執(zhí)行上下文放入執(zhí)行上下文棧中,同樣,在函數(shù)執(zhí)行上下文里面,會(huì)有一個(gè)VO(Variable Object)變量對象,這里的VO其實(shí)指向AO(Activation Object),這里的AO類似于GO,只不過它不是全局的,而是函數(shù)特有的,在執(zhí)行函數(shù)內(nèi)部代碼前,即編譯階段,也會(huì)將變量賦值為undefined,如果里面嵌套函數(shù),類似GO,會(huì)以函數(shù)名作為GO的一個(gè)屬性名,值為存儲(chǔ)這個(gè)函數(shù)空間的內(nèi)存地址,在這個(gè)函數(shù)空間中,會(huì)有函數(shù)的執(zhí)行體(代碼段),還會(huì)存儲(chǔ)這個(gè)函數(shù)父級(jí)作用域,然后執(zhí)行時(shí),將變量賦值,如果里面嵌套的函數(shù)被執(zhí)行,也會(huì)創(chuàng)建函數(shù)執(zhí)行上下文,并且這個(gè)函數(shù)執(zhí)行上下文放入執(zhí)行上下文棧中。
作用域鏈
其實(shí)在創(chuàng)建VO對象時(shí),也會(huì)在函數(shù)執(zhí)行上下文中創(chuàng)建作用域鏈,這個(gè)作用域鏈包括,自身的變量對象(VO)和父級(jí)作用域,當(dāng)我們查找一個(gè)變量時(shí),真實(shí)的查找路徑是沿著作用域鏈來查找
這段代碼中,顯然name會(huì)順著作用域鏈查找到“why”,然后顯然在foo 函數(shù)編譯未執(zhí)行階段,m=undefined,然后執(zhí)行,m輸出的應(yīng)該是undefined,如下圖是代碼的執(zhí)行邏輯圖。
這里的message輸出的應(yīng)該是Hello Global,foo函數(shù)在全局初始化時(shí)父級(jí)作用域已經(jīng)為全局了,然后foo函數(shù)執(zhí)行時(shí),找不到message變量便會(huì)去父級(jí)作用域去尋找,也就是全局作用域,所以輸出的是Hello Global,執(zhí)行邏輯圖如下。
上圖的輸出是undefined而不是100,就因?yàn)閒oo函數(shù)在解析時(shí)碰到return var a=100已經(jīng)認(rèn)為定義了一個(gè)a,賦值為undefined,但在執(zhí)行時(shí)卻不會(huì)執(zhí)行到這一步。
這里要提一下,如果變量在定義時(shí),未加任何約束。
比如通常來說定義一個(gè)變量是 var name=“anonymous”,let name="anonymous"如果直接寫成name=”anonymous“,在其他語言中,這肯定會(huì)報(bào)錯(cuò),但是在JavaScript中,允許這種寫法,并且這種寫法定義的變量會(huì)直接加到GO里面。
function foo(){ var a=b=10 } foo() console.log(a,b)
var a=b=10<==>等同于var a=10;b=10;
這樣的話b放入GO中,在全局輸出值為10;而a僅在函數(shù)中被定義,在全局輸出顯然會(huì)報(bào)錯(cuò)。
內(nèi)存管理
不管什么樣的編程語言,在代碼的執(zhí)行過程中都是需要給它分配內(nèi)存的,不同的是某些編程語需要我們自己手動(dòng)的管理內(nèi)存,某些編程語言會(huì)可以自動(dòng)幫助我們管理內(nèi)存:
不管以什么樣的方式來管理內(nèi)存,內(nèi)存的管理都會(huì)有如下的生命周期:
- 第一步:分配申請你需要的內(nèi)存(申請)
- 第二步:使用分配的內(nèi)存(存放一些東西,比如對象等) ;
- 第三步:不需要使用時(shí),對其進(jìn)行釋放;
不同的編程語言對于第一步和第三步會(huì)有不同的實(shí)現(xiàn):
手動(dòng)管理內(nèi)存:比如C、 C++ ,都是需要 手動(dòng)來管理內(nèi)存的申請和釋放的 (malloc和free)
自動(dòng)管理內(nèi)存:比如Java、JavaScript. Python. Swift、 Dart等 ,它們有自動(dòng)幫助我們管理內(nèi)存;
引用計(jì)數(shù)
當(dāng)一個(gè)對象有一個(gè)引用指向它時(shí),那么這個(gè)對象的引用就+1 ,當(dāng)一個(gè)對象的引用為0時(shí),這個(gè)對象就可以被銷毀掉;
這個(gè)算法有一個(gè)很大的弊端就是會(huì)產(chǎn)生循環(huán)引用:
var obj1={info:obj2}; var obj2={info:obj1}
標(biāo)記清除
這個(gè)算法是設(shè)置一個(gè)根對象( root object) ,垃圾回收器會(huì)定期從這個(gè)根開始,找所有從根開始有引用到的對象,對于哪些沒有引用到的對象,就認(rèn)為是不可用的對象;
這個(gè)算法可以很好的解決循環(huán)弓|用的問題;
JS引擎比較廣泛的采用的就是標(biāo)記清除算法,當(dāng)然類似于V8弓|擎為了進(jìn)行更好的優(yōu)化,它在算法的實(shí)現(xiàn)細(xì)節(jié)上也會(huì)結(jié)合一些其他的算法。
以上就是JavaScript函數(shù)執(zhí)行、作用域鏈以及內(nèi)存管理詳解的詳細(xì)內(nèi)容,更多關(guān)于函數(shù)執(zhí)行、作用域鏈以及內(nèi)存管理的資料請關(guān)注腳本之家其它相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章
基于javascript實(shí)現(xiàn)圖片左右切換效果
這篇文章主要為大家介紹了基于javascript實(shí)現(xiàn)圖片左右切換效果,感興趣的小伙伴們可以參考一下2016-01-01微信小程序?qū)崿F(xiàn)歷史搜索功能的全過程(h5同理)
最近在使用微信小程序開發(fā)的時(shí)候遇到了一個(gè)需求,需要實(shí)現(xiàn)歷史搜索記錄的功能,所以下面這篇文章主要給大家介紹了關(guān)于微信小程序?qū)崿F(xiàn)歷史搜索功能(h5同理)的相關(guān)資料,需要的朋友可以參考下2022-12-12JS實(shí)現(xiàn)隨機(jī)數(shù)生成算法示例代碼
JS實(shí)現(xiàn)隨機(jī)數(shù)生成算法的方法有很多,本文為大家介紹一個(gè)比較不錯(cuò)的方法,代碼如下,感興趣的朋友可以參考下,希望對大家有所幫助2013-08-08如何在父窗口中得知window.open()出的子窗口關(guān)閉事件
在父窗口中得知window.open()出的子窗口關(guān)閉事件的方法有很多,在本文將為大家詳細(xì)介紹下,感興趣的朋友可以參考下2013-10-10Bootstrap實(shí)現(xiàn)模態(tài)框效果
這篇文章主要為大家詳細(xì)介紹了Bootstrap實(shí)現(xiàn)模態(tài)框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09JS實(shí)現(xiàn)頁面載入時(shí)隨機(jī)顯示圖片效果
這篇文章主要介紹了JS實(shí)現(xiàn)頁面載入時(shí)隨機(jī)顯示圖片效果,涉及javascript基于隨機(jī)數(shù)與數(shù)組的頁面元素動(dòng)態(tài)修改相關(guān)操作技巧,需要的朋友可以參考下2016-09-09js防抖函數(shù)和節(jié)流函數(shù)使用場景和實(shí)現(xiàn)區(qū)別示例分析
這篇文章主要介紹了js防抖函數(shù)和節(jié)流函數(shù)使用場景和實(shí)現(xiàn)區(qū)別,結(jié)合實(shí)例形式詳細(xì)分析了js防抖函數(shù)和節(jié)流函數(shù)基本功能、定義、用法區(qū)別及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04js substr、substring和slice使用說明小記
關(guān)于substr、substring和slice方法區(qū)別的文章,網(wǎng)上搜到了許多,文章內(nèi)容也基本一致。而后,我將其中一篇文章中的代碼挪到本地進(jìn)行了測試,發(fā)現(xiàn)測試結(jié)果和原文中的有些出入。2011-09-09