JavaScript函數(shù)執(zhí)行、作用域鏈以及內(nèi)存管理詳解
前言
在我們平常編寫(xiě)JavaScript代碼的時(shí)候,難免會(huì)用到函數(shù),函數(shù)里面會(huì)有各種變量,這些變量的作用的范圍,以及在使用內(nèi)存存儲(chǔ)這些變量時(shí),內(nèi)存管理的問(wèn)題,在平時(shí)編程亦或者面試時(shí),多多少少都會(huì)遇到,所以這篇文章針對(duì)這三個(gè)問(wèn)題,進(jìn)行了深入的探討。
函數(shù)執(zhí)行
首先說(shuō)一下JavaScript執(zhí)行代碼的順序,JavaScript在執(zhí)行一段可執(zhí)行代碼的時(shí)候,會(huì)創(chuàng)建一個(gè)執(zhí)行上下文棧(Execution Context Stack 簡(jiǎn)稱(chēng)ECStack),執(zhí)行全局代碼時(shí)創(chuàng)建的全局執(zhí)行上下文(Global Execution Context 簡(jiǎn)稱(chēng)GEC),以及執(zhí)行函數(shù)時(shí)創(chuàng)建的函數(shù)執(zhí)行上下文(Function Execution Context 簡(jiǎn)稱(chēng)FEC),在運(yùn)行時(shí)都會(huì)按順序放入棧中,而不管是全局執(zhí)行上下文還是函數(shù)執(zhí)行上下文在創(chuàng)建時(shí)都會(huì)有一個(gè)變量對(duì)象(variable Object)。
全局執(zhí)行上下文
在JavaScript執(zhí)行全局代碼時(shí),會(huì)創(chuàng)建一個(gè)全局執(zhí)行上下文,放入執(zhí)行上下文棧,還有一個(gè)GlobalObject(GO),全局執(zhí)行上下文中會(huì)有一個(gè)變量對(duì)象(variable Object),指向GO。
在編譯階段,GO會(huì)對(duì)在全局定義的變量初始化為undefined,當(dāng)遇到函數(shù)時(shí),便會(huì)以函數(shù)名作為GO的一個(gè)屬性名,值為存儲(chǔ)這個(gè)函數(shù)空間的內(nèi)存地址,在這個(gè)函數(shù)空間中,會(huì)有函數(shù)的執(zhí)行體(代碼段),還會(huì)存儲(chǔ)這個(gè)函數(shù)父級(jí)作用域。
編譯完成后,代碼開(kāi)始執(zhí)行,便會(huì)對(duì)這些變量賦值,當(dāng)然里面除了這些,還有一些全局的對(duì)象和函數(shù),比如setTimeout,Date,String等等,還有一個(gè)屬性window賦值為this,當(dāng)遇到函數(shù)時(shí),便會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文放入執(zhí)行上下文棧中。
函數(shù)執(zhí)行上下文
上文說(shuō)到,代碼執(zhí)行時(shí)候,執(zhí)行到函數(shù)時(shí),會(huì)創(chuàng)建函數(shù)執(zhí)行上下文,并且函數(shù)執(zhí)行上下文放入執(zhí)行上下文棧中,同樣,在函數(shù)執(zhí)行上下文里面,會(huì)有一個(gè)VO(Variable Object)變量對(duì)象,這里的VO其實(shí)指向AO(Activation Object),這里的AO類(lèi)似于GO,只不過(guò)它不是全局的,而是函數(shù)特有的,在執(zhí)行函數(shù)內(nèi)部代碼前,即編譯階段,也會(huì)將變量賦值為undefined,如果里面嵌套函數(shù),類(lèi)似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對(duì)象時(shí),也會(huì)在函數(shù)執(zhí)行上下文中創(chuàng)建作用域鏈,這個(gè)作用域鏈包括,自身的變量對(duì)象(VO)和父級(jí)作用域,當(dāng)我們查找一個(gè)變量時(shí),真實(shí)的查找路徑是沿著作用域鏈來(lái)查找

這段代碼中,顯然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í),未加任何約束。
比如通常來(lái)說(shuō)定義一個(gè)變量是 var name=“anonymous”,let name="anonymous"如果直接寫(xiě)成name=”anonymous“,在其他語(yǔ)言中,這肯定會(huì)報(bào)錯(cuò),但是在JavaScript中,允許這種寫(xiě)法,并且這種寫(xiě)法定義的變量會(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)存管理
不管什么樣的編程語(yǔ)言,在代碼的執(zhí)行過(guò)程中都是需要給它分配內(nèi)存的,不同的是某些編程語(yǔ)需要我們自己手動(dòng)的管理內(nèi)存,某些編程語(yǔ)言會(huì)可以自動(dòng)幫助我們管理內(nèi)存:
不管以什么樣的方式來(lái)管理內(nèi)存,內(nèi)存的管理都會(huì)有如下的生命周期:
- 第一步:分配申請(qǐng)你需要的內(nèi)存(申請(qǐng))
- 第二步:使用分配的內(nèi)存(存放一些東西,比如對(duì)象等) ;
- 第三步:不需要使用時(shí),對(duì)其進(jìn)行釋放;
不同的編程語(yǔ)言對(duì)于第一步和第三步會(huì)有不同的實(shí)現(xiàn):
手動(dòng)管理內(nèi)存:比如C、 C++ ,都是需要 手動(dòng)來(lái)管理內(nèi)存的申請(qǐng)和釋放的 (malloc和free)
自動(dòng)管理內(nèi)存:比如Java、JavaScript. Python. Swift、 Dart等 ,它們有自動(dòng)幫助我們管理內(nèi)存;
引用計(jì)數(shù)
當(dāng)一個(gè)對(duì)象有一個(gè)引用指向它時(shí),那么這個(gè)對(duì)象的引用就+1 ,當(dāng)一個(gè)對(duì)象的引用為0時(shí),這個(gè)對(duì)象就可以被銷(xiāo)毀掉;
這個(gè)算法有一個(gè)很大的弊端就是會(huì)產(chǎn)生循環(huán)引用:
var obj1={info:obj2};
var obj2={info:obj1}標(biāo)記清除
這個(gè)算法是設(shè)置一個(gè)根對(duì)象( root object) ,垃圾回收器會(huì)定期從這個(gè)根開(kāi)始,找所有從根開(kāi)始有引用到的對(duì)象,對(duì)于哪些沒(méi)有引用到的對(duì)象,就認(rèn)為是不可用的對(duì)象;
這個(gè)算法可以很好的解決循環(huán)弓|用的問(wèn)題;
JS引擎比較廣泛的采用的就是標(biāo)記清除算法,當(dāng)然類(lèi)似于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)存管理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)圖片預(yù)加載的方法總結(jié)
在Web前端開(kāi)發(fā)中,圖片是網(wǎng)頁(yè)內(nèi)容的重要組成部分,但它們的加載時(shí)間往往會(huì)影響頁(yè)面的整體性能,為了提升用戶(hù)體驗(yàn),減少等待時(shí)間,開(kāi)發(fā)者通常會(huì)采用圖片預(yù)加載(Image Preloading)技術(shù),本文給大家介紹了JavaScript實(shí)現(xiàn)圖片預(yù)加載的方法總結(jié),需要的朋友可以參考下2024-12-12
JavaScript 如何計(jì)算文本的行數(shù)的實(shí)現(xiàn)
這篇文章主要介紹了JavaScript 如何計(jì)算文本的行數(shù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
js使用Canvas將多張圖片合并成一張的實(shí)現(xiàn)代碼
這篇文章主要介紹了js使用Canvas將多張圖片合并成一張的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
JS添加刪除一組文本框并對(duì)輸入信息加以驗(yàn)證判斷其正確性
需要添加幾組數(shù)據(jù)到數(shù)據(jù)庫(kù),但是具體幾組數(shù)據(jù)不確定,有客戶(hù)來(lái)填寫(xiě),所以,這里我用JS進(jìn)行添加刪除子方案,并要對(duì)方案輸入的正確性加以判斷,感興趣的朋友可以了解下2013-04-04
IE8中動(dòng)態(tài)創(chuàng)建script標(biāo)簽onload無(wú)效的解決方法
這篇文章主要介紹了IE8中動(dòng)態(tài)創(chuàng)建script標(biāo)簽onload無(wú)效的解決方法,涉及針對(duì)javascript加載順序的調(diào)整,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2014-12-12
前端內(nèi)網(wǎng)開(kāi)發(fā)npm安裝的幾種方法小結(jié)
這篇文章主要介紹了如何在不聯(lián)網(wǎng)或離線環(huán)境下安裝npm依賴(lài),文中通過(guò)代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2025-01-01
window.parent調(diào)用父框架時(shí) ie跟火狐不兼容問(wèn)題
window.parent調(diào)用父框架時(shí),ie跟火狐不兼容問(wèn)題!2009-07-07
JavaScript中的appendChild()方法示例詳解
這篇文章主要介紹了JavaScript中的appendChild()方法,appendChild()方法是向節(jié)點(diǎn)添加最后一個(gè)子節(jié)點(diǎn),也可以使用此方法從一個(gè)元素向另一個(gè)元素移動(dòng)元素,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10
Boostrap模態(tài)窗口的學(xué)習(xí)小結(jié)
Bootstrap Modals(模態(tài)框)是使用定制的 Jquery 插件創(chuàng)建的。它可以用來(lái)創(chuàng)建模態(tài)窗口豐富用戶(hù)體驗(yàn),或者為用戶(hù)添加實(shí)用功能。您可以在 Modals(模態(tài)框)中使用 Popover(彈出框)和 Tooltip(工具提示插件)2016-03-03
JavaScript的Object.defineProperty詳解
本篇文章給大家詳細(xì)講述了JavaScript的Object.defineProperty的相關(guān)知識(shí)點(diǎn)內(nèi)容,有興趣的朋友參考學(xué)習(xí)下。2018-07-07

