深入學(xué)習(xí)JavaScript執(zhí)行上下文
前言
我們先不看這個(gè)標(biāo)題,來(lái)看下面這段代碼是怎么運(yùn)行的:
var num1=1 var num1=2 var result=num1+num2 console.log(result)
我相信,小伙伴都知道打印的是3把,這也太簡(jiǎn)單了,但是我們思考下,這段代碼執(zhí)行時(shí),到底進(jìn)行了怎樣的操作,這里我們要引進(jìn)一個(gè)概念:初始化全局對(duì)象和執(zhí)行上下文
初始化全局對(duì)象(GO)
JS引擎在解析代碼的時(shí)候,會(huì)在堆內(nèi)存中創(chuàng)建一個(gè)全局對(duì)象叫:Gobal Object(GO)
- 該對(duì)象所有的作用域都可以訪(fǎng)問(wèn)
- 里面會(huì)包含Date Array String Number setTimeout setInterval 等等
- 還有一個(gè)windows屬性指向本身

執(zhí)行上下文
代碼運(yùn)行時(shí):js引擎(這里考慮V8引擎)會(huì)創(chuàng)建執(zhí)行上下文棧(Execution context stack)簡(jiǎn)稱(chēng)ECS,它就是執(zhí)行代碼的調(diào)用棧
執(zhí)行上下文分為:
- 全局執(zhí)行上下文( GEC )
- 函數(shù)執(zhí)行上下文( FEC )
這里我們先分析下全局執(zhí)行上下文,因?yàn)槲覀冮_(kāi)頭那段代碼是全局代碼
全局執(zhí)行上下文
在執(zhí)行全局代碼的時(shí)候,會(huì)創(chuàng)建全局執(zhí)行上下文( GEC ),GEC會(huì)被放入ECS中去執(zhí)行 GEC被放入ECS
包含兩部分:
- 第一部分:在代碼執(zhí)行前,也就是V8引擎將parser轉(zhuǎn)成AST的過(guò)程中 ( 這里會(huì)涉及到V8引擎在解析js代碼的過(guò)程,會(huì)開(kāi)篇專(zhuān)題講這個(gè)),會(huì)將全局定義的變量放入GO中,但是并不會(huì)賦值,因?yàn)槭?strong>解析階段 (pass:這個(gè)過(guò)程也被叫做變量的作用域提升)
- 第二部分:執(zhí)行代碼,并為GO里面的變量賦值,或者執(zhí)行其他函數(shù) 流程圖如下:圖中執(zhí)行前是上面描述的第一部分,開(kāi)始執(zhí)行代碼是第二部分

Java Script遇到函數(shù)代碼如何執(zhí)行?
當(dāng)js引擎遇到函數(shù)執(zhí)行時(shí),會(huì)創(chuàng)建一個(gè) 函數(shù)執(zhí)行上下文( Functional Execution Contex ) 簡(jiǎn)稱(chēng)FEC,并壓入到執(zhí)行上下文棧ECS
FEC中包含三部分內(nèi)容:
- 第一部分:VO(variable object ) 對(duì)象,其實(shí)也是AO( Activation Object )
- 第二部分:作用域鏈(scope chain):由自己的VO+父級(jí)的VO,查找時(shí)會(huì)一層一層去查找
- 第三部分:this綁定( 這里不做詳細(xì)描述,后續(xù)會(huì)出專(zhuān)題 )
作用域是解析編譯的時(shí)候就決定了,并不是執(zhí)行調(diào)用的時(shí)候來(lái)我們看一段代碼,一起看一下函數(shù)的執(zhí)行過(guò)程,一起來(lái)思考下這段代碼的運(yùn)行結(jié)果
var message = "Global"
function foo() {
console.log(message)
}
function bar() {
var message = "Bar"
foo()
}
bar()畫(huà)個(gè)圖分析一下吧
首先初始化全局對(duì)象GO,里面有Array、date、setTime等等,還有自己定義的 message對(duì)象初始值為undefined,foo和bar函數(shù)對(duì)象


開(kāi)始執(zhí)行代碼前,會(huì)創(chuàng)建一個(gè)執(zhí)行上下文棧ESC,開(kāi)始執(zhí)行全局代碼,所以會(huì)創(chuàng)建一個(gè)全局執(zhí)行上下文棧GEC,將GEC壓入棧底,全局執(zhí)行上下文包括兩部分:
第一部分代碼執(zhí)行前的VO對(duì)象(這里的VO指向的是GO)
第二部分是開(kāi)始執(zhí)行代碼,執(zhí)行第一行時(shí) var message="Global",GO對(duì)象里的message就被改為Global

執(zhí)行完第一行后,開(kāi)始執(zhí)行第9行代碼 bar(),這里是函數(shù)的調(diào)用執(zhí)行,js引擎會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文FEC,壓入棧中,FEC包含三部分:
第一部分:VO對(duì)象,這里指向bar自己的AO對(duì)象(包括形參和函數(shù)中定義的變量)
第二部分:作用域鏈scope chain,自己的VO對(duì)象+parent VO對(duì)象
第三部分:this綁定,這里是指向windows(這個(gè)后續(xù)會(huì)開(kāi)專(zhuān)題講)
開(kāi)始執(zhí)行bar函數(shù)里面的代碼,var message="Bar",會(huì)將bar的AO對(duì)象的message變量值從undefined改為"Bar",接下來(lái)在執(zhí)行foo()函數(shù),注意:此時(shí)bar函數(shù)還沒(méi)彈出棧,因?yàn)閒oo函數(shù)還在執(zhí)行

訪(fǎng)問(wèn)一個(gè)變量的時(shí)候,會(huì)沿著作用域鏈一層一層往上找,最后沒(méi)有找到則會(huì)報(bào)錯(cuò)
執(zhí)行到bar函數(shù)的最后一行代碼是foo(),此時(shí)又是一個(gè)函數(shù)的調(diào)用執(zhí)行,又會(huì)創(chuàng)建一個(gè)foo的函數(shù)執(zhí)行上下文,也包含上述的三部分:VO對(duì)象(指向foo的AO對(duì)象) 作用域鏈 和this綁定
開(kāi)始執(zhí)行foo函數(shù)代碼console.log(message),當(dāng)打印message的時(shí)候,會(huì)沿著作用域鏈一層層找,foo的作用域鏈?zhǔn)亲约旱腁O+父級(jí)的VO(也就是GO對(duì)象),自己的AO對(duì)象為空,所以找到GO里面的message變量,最終打印"Global"

執(zhí)行完后,函數(shù)執(zhí)行上下文執(zhí)行完之后,就會(huì)彈出棧,foo的FEC先彈出棧,然后bar的FEC彈出棧,他們自個(gè)的AO對(duì)象最終也被釋放
環(huán)境變量和記錄
我們上述所說(shuō)的VO是基于早期的ECMAS規(guī)范,官網(wǎng)是這樣說(shuō)的:

這里借助coderwhy的翻譯:每個(gè)執(zhí)行上下文都關(guān)聯(lián)一個(gè)變量對(duì)象(Vriable Object),在源代碼中變量和函數(shù)的聲明會(huì)被作為屬性加入到變量對(duì)象中,對(duì)于函數(shù)來(lái)說(shuō),參數(shù)也會(huì)被加入到VO中但是ECMA5以后的版本,官網(wǎng)做了一些詞匯用語(yǔ)的修改

每個(gè)執(zhí)行上下文都會(huì)關(guān)聯(lián)一個(gè)變量環(huán)境(variable Environment),在執(zhí)行代碼中變量和函數(shù)的聲明會(huì)被當(dāng)做環(huán)境記錄(Environment Record)加入到變量環(huán)境中。
對(duì)于函數(shù)代碼,形參也會(huì)被當(dāng)做環(huán)境記錄加入到變量環(huán)境中
總結(jié):
- 在訪(fǎng)問(wèn)一個(gè)變量時(shí),會(huì)沿著作用域鏈一層層往上找,最終沒(méi)找到,會(huì)報(bào)錯(cuò):未定義
- 作用域以及父級(jí)作用域是代碼在編譯階段就已經(jīng)確定了,和調(diào)用位置沒(méi)關(guān)系
匯總一些名詞解釋?zhuān)覀儊?lái)解釋一下:
| 名詞 | 解釋 |
|---|---|
| ECS | 執(zhí)行上下文棧 (Execution Context Stack),也可稱(chēng)為調(diào)用棧 |
| GEC | 全局執(zhí)行上下文(Global Execution Context),在執(zhí)行全局代碼前創(chuàng)建 |
| FEC | 函數(shù)執(zhí)行上下文(Functional Execution Context),在執(zhí)行函數(shù)前創(chuàng)建 |
| VO | Variable Object,早期ECMA規(guī)范中的變量環(huán)境,對(duì)應(yīng)Object |
| VE | Variable Environment,最新ECMA規(guī)范中的變量環(huán)境,對(duì)應(yīng)環(huán)境記錄 |
| GO | 全局對(duì)象(Global Object),解析全局代碼時(shí)創(chuàng)建,GEC中關(guān)聯(lián)的VO就是GO |
| AO | 活動(dòng)對(duì)象(Activation Object),VO被激活就變成了AO,VO和AO本質(zhì)上是一個(gè)東西,它們其實(shí)都是同一個(gè)對(duì)象,只是處于執(zhí)行環(huán)境的不同生命周期 |
到此這篇關(guān)于深入學(xué)習(xí)JavaScript執(zhí)行上下文的文章就介紹到這了,更多相關(guān)JS執(zhí)行上下文內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- js bind 函數(shù) 使用閉包保存執(zhí)行上下文
- JS ES6中setTimeout函數(shù)的執(zhí)行上下文示例
- 深入理解JavaScript 中的執(zhí)行上下文和執(zhí)行棧
- 一篇文章弄懂javascript中的執(zhí)行棧與執(zhí)行上下文
- JavaScript ECMA-262-3 深入解析(一):執(zhí)行上下文實(shí)例分析
- 通過(guò)實(shí)例了解JS執(zhí)行上下文運(yùn)行原理
- Javascript執(zhí)行上下文順序的深入講解
- js 執(zhí)行上下文和作用域的相關(guān)總結(jié)
- 詳解JavaScript中的執(zhí)行上下文及調(diào)用堆棧
相關(guān)文章
javascript table排序 這個(gè)更簡(jiǎn)單了,不用改變現(xiàn)在的表格結(jié)構(gòu)
另外一個(gè)table排序,這個(gè)更簡(jiǎn)單了,不用改變現(xiàn)在的表格結(jié)構(gòu),來(lái)自國(guó)外的代碼。2010-04-04
AutoJs4.4.1免費(fèi)版快速接通vscode調(diào)試腳本的操作方法
這篇文章主要介紹了AutoJs4.4.1免費(fèi)版快速接通vscode進(jìn)行調(diào)試腳本,首先下載AutoJs并安裝,下載完成后,將2個(gè)apk文件拷貝到手機(jī)安裝即可,接下來(lái)需要安裝插件,本文分步驟給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
手寫(xiě)Spirit防抖函數(shù)underscore和節(jié)流函數(shù)lodash
這篇文章主要介紹了手寫(xiě)Spirit防抖函數(shù)underscore和節(jié)流函數(shù)lodash,接下來(lái)將會(huì)帶你們了解下這兩者的區(qū)別,以及我們?cè)撊绾问謱?xiě)實(shí)現(xiàn)這兩個(gè)函數(shù)2022-03-03
純js代碼實(shí)現(xiàn)簡(jiǎn)單計(jì)算器
這篇文章主要介紹了純js代碼實(shí)現(xiàn)簡(jiǎn)單計(jì)算器,功能超簡(jiǎn)單,實(shí)現(xiàn)加減乘除簡(jiǎn)單運(yùn)算,感興趣的小伙伴們可以參考一下2015-12-12
JavaScript中報(bào)錯(cuò)Cannot?set?properties?of?undefined?(settin
這篇文章主要介紹了JavaScript中報(bào)錯(cuò)Cannot?set?properties?of?undefined?(setting?'1')的解決方案,文中通過(guò)代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或使用js具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2025-03-03
JS加密插件CryptoJS實(shí)現(xiàn)的DES加密示例
這篇文章主要介紹了JS加密插件CryptoJS實(shí)現(xiàn)的DES加密,結(jié)合實(shí)例形式分析了javascript使用CryptoJS插件進(jìn)行des加密的各種常見(jiàn)模式使用技巧,需要的朋友可以參考下2018-08-08
js與jquery分別實(shí)現(xiàn)tab標(biāo)簽頁(yè)功能的方法
這篇文章主要介紹了js與jquery分別實(shí)現(xiàn)tab標(biāo)簽頁(yè)功能的方法,結(jié)合實(shí)例形式對(duì)比分析了javascript與jQuery分別實(shí)現(xiàn)tab標(biāo)簽功能的操作技巧,需要的朋友可以參考下2016-11-11
JavaScript使用SpreadJS創(chuàng)建Excel查看器
在現(xiàn)代的Web應(yīng)用開(kāi)發(fā)中,Excel文件的處理和展示是一項(xiàng)常見(jiàn)的需求,小編今天將為大家展示如何借助SpreadJS來(lái)創(chuàng)建一個(gè)Excel查看器,感興趣的小伙伴可以了解下2023-12-12

