深入學習JavaScript執(zhí)行上下文
前言
我們先不看這個標題,來看下面這段代碼是怎么運行的:
var num1=1 var num1=2 var result=num1+num2 console.log(result)
我相信,小伙伴都知道打印的是3把,這也太簡單了,但是我們思考下,這段代碼執(zhí)行時,到底進行了怎樣的操作,這里我們要引進一個概念:初始化全局對象和執(zhí)行上下文
初始化全局對象(GO)
JS引擎在解析代碼的時候,會在堆內存中創(chuàng)建一個全局對象叫:Gobal Object(GO)
- 該對象所有的作用域都可以訪問
- 里面會包含Date Array String Number setTimeout setInterval 等等
- 還有一個windows屬性指向本身
執(zhí)行上下文
代碼運行時:js引擎(這里考慮V8引擎)會創(chuàng)建執(zhí)行上下文棧(Execution context stack)簡稱ECS,它就是執(zhí)行代碼的調用棧
執(zhí)行上下文分為:
- 全局執(zhí)行上下文( GEC )
- 函數(shù)執(zhí)行上下文( FEC )
這里我們先分析下全局執(zhí)行上下文,因為我們開頭那段代碼是全局代碼
全局執(zhí)行上下文
在執(zhí)行全局代碼的時候,會創(chuàng)建全局執(zhí)行上下文( GEC ),GEC會被放入ECS中去執(zhí)行 GEC被放入ECS
包含兩部分:
- 第一部分:在代碼執(zhí)行前,也就是V8引擎將parser轉成AST的過程中 ( 這里會涉及到V8引擎在解析js代碼的過程,會開篇專題講這個),會將全局定義的變量放入GO中,但是并不會賦值,因為是解析階段 (pass:這個過程也被叫做變量的作用域提升)
- 第二部分:執(zhí)行代碼,并為GO里面的變量賦值,或者執(zhí)行其他函數(shù) 流程圖如下:圖中執(zhí)行前是上面描述的第一部分,開始執(zhí)行代碼是第二部分
Java Script遇到函數(shù)代碼如何執(zhí)行?
當js引擎遇到函數(shù)執(zhí)行時,會創(chuàng)建一個 函數(shù)執(zhí)行上下文( Functional Execution Contex ) 簡稱FEC,并壓入到執(zhí)行上下文棧ECS
FEC中包含三部分內容:
- 第一部分:VO(variable object ) 對象,其實也是AO( Activation Object )
- 第二部分:作用域鏈(scope chain):由自己的VO+父級的VO,查找時會一層一層去查找
- 第三部分:this綁定( 這里不做詳細描述,后續(xù)會出專題 )
作用域是解析編譯的時候就決定了,并不是執(zhí)行調用的時候來我們看一段代碼,一起看一下函數(shù)的執(zhí)行過程,一起來思考下這段代碼的運行結果
var message = "Global" function foo() { console.log(message) } function bar() { var message = "Bar" foo() } bar()
畫個圖分析一下吧
首先初始化全局對象GO
,里面有Array、date、setTime
等等,還有自己定義的 message對象初始值為undefined,foo和bar函數(shù)對象
開始執(zhí)行代碼前,會創(chuàng)建一個執(zhí)行上下文棧ESC
,開始執(zhí)行全局代碼,所以會創(chuàng)建一個全局執(zhí)行上下文棧GEC
,將GEC壓入棧底,全局執(zhí)行上下文包括兩部分:
第一部分代碼執(zhí)行前的VO對象
(這里的VO指向的是GO
)
第二部分是開始執(zhí)行代碼,執(zhí)行第一行時 var message="Global"
,GO對象里的message就被改為Global
執(zhí)行完第一行后,開始執(zhí)行第9行代碼 bar(),這里是函數(shù)的調用執(zhí)行,js引擎會創(chuàng)建一個函數(shù)執(zhí)行上下文FEC,壓入棧中,FEC包含三部分:
第一部分:VO對象
,這里指向bar自己的AO對象(包括形參和函數(shù)中定義的變量
)
第二部分:作用域鏈scope chain
,自己的VO對象+parent VO對象
第三部分:this綁定
,這里是指向windows(這個后續(xù)會開專題講)
開始執(zhí)行bar函數(shù)里面的代碼,var message="Bar"
,會將bar的AO對象的message變量值從undefined改為"Bar"
,接下來在執(zhí)行foo()函數(shù),注意:此時bar函數(shù)還沒彈出棧,因為foo函數(shù)還在執(zhí)行
訪問一個變量的時候,會沿著作用域鏈一層一層往上找,最后沒有找到則會報錯
執(zhí)行到bar函數(shù)的最后一行代碼是foo(),此時又是一個函數(shù)的調用執(zhí)行,又會創(chuàng)建一個foo的函數(shù)執(zhí)行上下文,也包含上述的三部分:VO對象(指向foo的AO對象) 作用域鏈 和this綁定
開始執(zhí)行foo函數(shù)代碼console.log(message)
,當打印message的時候,會沿著作用域鏈一層層找
,foo的作用域鏈是自己的AO+父級的VO(也就是GO對象)
,自己的AO對象為空,所以找到GO里面的message變量,最終打印"Global"
執(zhí)行完后,函數(shù)執(zhí)行上下文執(zhí)行完之后,就會彈出棧,foo的FEC先彈出棧,然后bar的FEC彈出棧,他們自個的AO對象最終也被釋放
環(huán)境變量和記錄
我們上述所說的VO是基于早期的ECMAS規(guī)范,官網(wǎng)是這樣說的:
這里借助coderwhy的翻譯:每個執(zhí)行上下文都關聯(lián)一個變量對象(Vriable Object
),在源代碼中變量和函數(shù)的聲明會被作為屬性加入到變量對象中,對于函數(shù)來說,參數(shù)也會被加入到VO中但是ECMA5以后的版本,官網(wǎng)做了一些詞匯用語的修改
每個執(zhí)行上下文都會關聯(lián)一個變量環(huán)境(variable Environment)
,在執(zhí)行代碼中變量和函數(shù)的聲明會被當做環(huán)境記錄(Environment Record)
加入到變量環(huán)境中。
對于函數(shù)代碼,形參也會被當做環(huán)境記錄加入到變量環(huán)境中
總結:
- 在訪問一個變量時,會沿著作用域鏈一層層往上找,最終沒找到,會報錯:未定義
- 作用域以及父級作用域是代碼在編譯階段就已經(jīng)確定了,和調用位置沒關系
匯總一些名詞解釋,我們來解釋一下:
名詞 | 解釋 |
---|---|
ECS | 執(zhí)行上下文棧 (Execution Context Stack),也可稱為調用棧 |
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)境,對應Object |
VE | Variable Environment,最新ECMA規(guī)范中的變量環(huán)境,對應環(huán)境記錄 |
GO | 全局對象(Global Object),解析全局代碼時創(chuàng)建,GEC中關聯(lián)的VO就是GO |
AO | 活動對象(Activation Object),VO被激活就變成了AO,VO和AO本質上是一個東西,它們其實都是同一個對象,只是處于執(zhí)行環(huán)境的不同生命周期 |
到此這篇關于深入學習JavaScript執(zhí)行上下文的文章就介紹到這了,更多相關JS執(zhí)行上下文內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Javascript實現(xiàn)的常用算法(如冒泡、快速、鴿巢、奇偶等)
這篇文章主要介紹了Javascript實現(xiàn)的常用算法,如冒泡、快速、鴿巢、選擇、木桶、奇偶等,需要的朋友可以參考下2014-04-04javascript十六進制數(shù)字和ASCII字符之間的轉換方法
下面小編就為大家?guī)硪黄猨avascript十六進制數(shù)字和ASCII字符之間的轉換方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12javascript使用正則表達式實現(xiàn)注冊登入校驗
這篇文章主要為大家詳細介紹了javascript使用正則表達式實現(xiàn)注冊登入校驗,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-09-09