javascript代碼解析過程
執(zhí)行上下文和作用域是javascript中非常重要的部分渤早,要弄清楚它們首先就要說到javascript的運行機制谤专,javascript代碼被解析經(jīng)過了以下幾個步驟
- Parser模塊將javascript源碼解析成抽象語法樹(AST)
- Ignition模塊將抽象語法樹編譯成字節(jié)碼(byteCode)训措,再編譯成機器碼
- 當函數(shù)被執(zhí)行多次時郑临,Ignition會記錄優(yōu)化信息颜骤,由Turbofan直接將抽象語法樹編譯成機器碼
全局上下文
了解完以上javascript運行機制之后聂渊,我們來看看以下全局代碼的執(zhí)行方式
console.log(user)
var user = 'alice'
var num = 16
console.log(num)
以上代碼經(jīng)過如下步驟才被執(zhí)行
-
javascript --> ast
- 全局創(chuàng)建一個GO( GlobalObject)對象孝宗,GO中有很多內(nèi)置模塊穷躁,如Math、String、以及window屬性问潭,其中window的值為this猿诸,也就是自身GO對象
- 全局定義的變量user和num會添加GO對象中,并賦值為undefined
-
ast --> Ignition
- V8引擎執(zhí)行代碼時狡忙,存在調(diào)用棧(ECStack)梳虽,此時創(chuàng)建全局上下文棧(Global Excution Context)
- GEC存在VO(variable Object),在全局上下文中指向GO對象
-
Ignition --> 運行結果
- 通過VO找到GO
- 將user賦值為alice灾茁,將num賦值為16
圖示如下
以上代碼的執(zhí)行的結果為
undefined
16
- parser模塊將源代碼編譯為AST時窜觉,已經(jīng)將user和num定義到VO對象中,值為undefined
- 打印user的時候北专,沒有執(zhí)行到user的賦值語句禀挫,所以user的值仍然為undefined
- 打印num的時候,已經(jīng)執(zhí)行了給num賦值的語句拓颓,所以num的值為16
函數(shù)上下文
定義函數(shù)的時候语婴,執(zhí)行方式和全局又有些不同
var name = 'alice'
foo(12)
function foo(num){
console.log(m)
var m = 10
var n = 20
console.log("foo")
}
以上代碼經(jīng)過如下步驟才被執(zhí)行
-
javascript --> ast
- 全局創(chuàng)建一個GO( GlobalObject)對象,GO中有很多內(nèi)置模塊驶睦,如Math砰左、String、以及window屬性啥繁,其中window的值為this菜职,也就是自身GO對象
- 全局定義的變量name會添加GO對象中青抛,并賦值為undefined
- 函數(shù)foo會開辟一塊內(nèi)存空間旗闽,比如為0x100,用來存儲父級作用域(parent scope)和自身代碼塊蜜另,函數(shù)foo的父級作用域就是全局對象GO
- 將foo添加到GO對象中适室,賦值為內(nèi)存地址,如0x100
-
ast --> Ignition
- V8引擎執(zhí)行代碼時举瑰,存在調(diào)用棧(ECStack)捣辆,此時創(chuàng)建全局上下文棧(Global Excution Context)
- GEC存在VO(variable Object),在全局上下文中指向GO對象
-
Ignition --> 運行結果
(1)執(zhí)行全局代碼- 通過VO找到GO
- 將name賦值為alice
- 執(zhí)行函數(shù)foo前此迅,創(chuàng)建函數(shù)執(zhí)行上下文(Function Excution Context)汽畴,存在VO指向AO對象
- 創(chuàng)建Activation Object,將num耸序、m都定義為undefined
(2) 執(zhí)行函數(shù)
* 將num賦值為12忍些,m賦值為10,n賦值為20
* 函數(shù)foo執(zhí)行完成坎怪,從調(diào)用棧(ECStack)棧頂彈出
圖示如下
所以上面代碼執(zhí)行結果為
undefined
預編譯
在Parser模塊將javascript源碼編譯成AST時罢坝,還經(jīng)過了一些細化的步驟
- Stram將源碼處理為統(tǒng)一的編碼格式
- Scanner進行詞法分析,將代碼轉(zhuǎn)成token
- token會被轉(zhuǎn)換成AST搅窿,經(jīng)過preparser和parser模塊
parser用來解析定義在全局的函數(shù)和變量嘁酿,定義在函數(shù)中的函數(shù)只會經(jīng)過預解析Preparser
閉包的執(zhí)行順序
var user = "alice"
foo(12)
function foo(num){
console.log(m)
var m = 10
function bar(){
console.log(user)
}
bar()
}
以上代碼經(jīng)過如下步驟才被執(zhí)行
-
javascript --> ast
- 全局創(chuàng)建一個 GO( GlobalObject)對象隙券,GO中有很多內(nèi)置模塊,如Math闹司、String娱仔、以及window屬性,其中window的值為this游桩,也就是自身GO對象
- 全局定義的變量user會添加GO對象中拟枚,并賦值為undefined
- 函數(shù)foo會開辟一塊內(nèi)存空間,比如為0x100众弓,用來存儲父級作用域(parent scope)和自身代碼塊恩溅,函數(shù)foo的父級作用域就是全局對象GO
- 將foo添加到GO對象中,賦值為內(nèi)存地址谓娃,如0x100
-
ast --> Ignition
- V8引擎執(zhí)行代碼時脚乡,存在調(diào)用棧(ECStack),此時創(chuàng)建全局上下文棧(Global Excution Context)
- GEC存在VO(variable Object)滨达,在全局上下文中指向GO對象
-
Ignition --> 運行結果
(1)執(zhí)行全局代碼- 通過VO找到GO
- 將user賦值為alice
- 執(zhí)行函數(shù)foo前奶稠,創(chuàng)建foo函數(shù)的執(zhí)行上下文(Function Excution Context),存在VO(variable Object)指向AO(Activation Object)對象
- 創(chuàng)建Activation Object捡遍,將num锌订、m都定義為undefined
- 為函數(shù)bar開辟內(nèi)存空間 0x200,用來存儲父級作用域和自身代碼画株,bar的父級作用域為函數(shù)foo的作用域AO+全局作用域GO
- 將bar添加到foo的AO對象中辆飘,賦值為內(nèi)存地址,0x200
(2) 執(zhí)行函數(shù)foo
- 將num賦值為12谓传,m賦值為10
(3) 執(zhí)行函數(shù)bar
- 創(chuàng)建bar的執(zhí)行上下文蜈项,存在VO(variable Object)指向AO(Activation Object)對象
- 創(chuàng)建Activation Object,此時AO為空對象
- 函數(shù)bar執(zhí)行完成续挟,從調(diào)用棧(ECStack)棧頂彈出
- 函數(shù)foo也執(zhí)行完成了紧卒,從調(diào)用棧(ECStack)棧頂彈出
所以上面代碼執(zhí)行結果為
undefined
alice
- m 在打印的時候還沒有被賦值,所以為undefined
-
打印user诗祸,首先在自己作用域中查找跑芳,沒有找到,往上在父級作用域foo的AO對象中查找直颅,還沒有找到博个,就找到了全局GO對象中
作用域
作用域是在解析成AST(抽象語法樹)的時候確定的,與它在哪里被調(diào)用沒有聯(lián)系
var message = "Hello Global"
function foo(){
console.log(message)
}
function bar(){
var message = "Hello Bar"
foo()
}
bar()
以上代碼經(jīng)過如下步驟才被執(zhí)行
-
javascript --> ast
- 全局創(chuàng)建一個 GO( GlobalObject)對象
- 全局定義的變量message會添加GO對象中际乘,并賦值為undefined
- 函數(shù)foo開辟一塊內(nèi)存空間坡倔,為0x100,用來存儲父級作用域(parent scope)和自身代碼塊,函數(shù)foo的父級作用域就是全局對象GO
- 將foo添加到GO對象中罪塔,賦值為內(nèi)存地址投蝉,0x100
- 函數(shù)bar開辟一塊內(nèi)存空間,為0x200征堪,用來存儲父級作用域(parent scope)和自身代碼塊瘩缆,函數(shù)foo的父級作用域就是全局對象GO
- 將bar添加到GO對象中,賦值為內(nèi)存地址佃蚜,0x200
-
ast --> Ignition
- V8引擎執(zhí)行代碼時庸娱,存在調(diào)用棧(ECStack),此時創(chuàng)建全局上下文棧(Global Excution Context)
- GEC存在VO(variable Object)谐算,在全局上下文中指向GO對象
-
Ignition --> 運行結果
(1)執(zhí)行全局代碼- 通過VO找到GO
- 將message賦值為Hello Global
- 執(zhí)行函數(shù)bar前熟尉,創(chuàng)建bar函數(shù)的執(zhí)行上下文(Function Excution Context),存在VO(variable Object)指向AO(Activation Object)對象
- 創(chuàng)建Activation Object洲脂,將message定義為undefined
(2) 執(zhí)行函數(shù)bar
- 將message賦值為Hello Bar
(3) 執(zhí)行函數(shù)foo
- 創(chuàng)建foo的執(zhí)行上下文斤儿,存在VO(variable Object)指向AO(Activation Object)對象
- 創(chuàng)建Activation Object,此時AO為空對象
- 打印 message恐锦,此時自己作用域內(nèi)沒有message往果,向上查找父級作用域,foo的父級作用域為GO
- 函數(shù)foo執(zhí)行完成一铅,從調(diào)用棧(ECStack)棧頂彈出
- 函數(shù)bar也執(zhí)行完成了陕贮,從調(diào)用棧(ECStack)棧頂彈出
所以最后輸出的結果為
Hello Gloabl
圖示如下
易混淆點
一、 沒有通過var標識符聲明的變量會被添加到全局
var n = 100
function foo(){
n = 200
}
foo()
console.log(n)
執(zhí)行過程如下
-
javascript --> ast
- GO對象中將n定義為undefined
- 開辟foo函數(shù)的內(nèi)存空間0x100潘飘,父級作用域為GO
- 將foo添加到GO對象中肮之,值為0x100
-
ast --> Ignition
- 創(chuàng)建全局上下文,VO指向GO
- 執(zhí)行foo函數(shù)前福也,創(chuàng)建函數(shù)上下文局骤,VO對象指向AO對象
- 創(chuàng)建AO對象攀圈,AO為空對象
-
賦值
- GO中的變量n被賦值為100
- 執(zhí)行foo函數(shù)中的賦值暴凑,因為沒有var標識符聲明,所以直接給全局GO中的n賦值200
所以此時執(zhí)行結果為
200
二赘来、函數(shù)作用域內(nèi)有變量现喳,就不會向父級作用域查找
function foo(){
console.log(n)
var n = 200
console.log(n)
}
var n = 100
foo()
執(zhí)行順序如下
-
javascript --> ast
- GO對象中添加變量n,值為undefined
- 為函數(shù)foo開辟內(nèi)存空間0x300犬辰,父級作用域為GO
- 將foo添加到GO對象中嗦篱,值為0x300
-
ast ---> Ignition
- 創(chuàng)建全局上下文,VO指向GO
- 執(zhí)行函數(shù)foo之前創(chuàng)建函數(shù)上下文幌缝,VO指向AO
- 創(chuàng)建AO對象灸促,添加變量n,值為undefined
-
賦值
- 將GO中的n賦值為100
- 執(zhí)行foo,打印n浴栽,此時先在自己的作用域內(nèi)查找是否存在變量n荒叼,AO中存在n值為undefined,所以不會再向父級作用域中查找
- 將AO中n賦值為200
- 打印n典鸡,此時自己作用域中存在n被廓,值為200
所以執(zhí)行結果為
undefined
200
三、return語句不影響ast的生成
在代碼解析階段萝玷,是不會受return語句的影響嫁乘,ast生成的過程中,只會去查找var 和 function標識符定義的內(nèi)容
var a = 100
function foo(){
console.log(a)
return
var a = 100
console.log(a)
}
foo()
執(zhí)行過程如下
-
javascript --> ast
- GO對象中將a定義為undefined
- 開辟foo函數(shù)的內(nèi)存空間0x400球碉,父級作用域為GO
- 將foo添加到GO對象中蜓斧,值為0x400
-
ast --> Ignition
- 創(chuàng)建全局上下文,VO指向GO
- 執(zhí)行foo函數(shù)前睁冬,創(chuàng)建函數(shù)上下文法精,VO對象指向AO對象
- 創(chuàng)建AO對象,將a添加到AO對象中痴突,值為undefined
-
賦值
- GO中的變量a被賦值為100
- 執(zhí)行foo函數(shù)搂蜓,打印a,此時a沒有被定義辽装,所以輸出undefined
- 執(zhí)行return帮碰,return后面的代碼不會執(zhí)行
所以執(zhí)行結果為
undefined
四、連等賦值
var a = b = 10拾积,相當于var a = 10; b = 10
function foo(){
var a = b = 10
}
foo()
console.log(b)
console.log(a)
執(zhí)行過程如下
-
javascript --> ast
- 創(chuàng)建GO對象殉挽,GO對象為空
- 開辟foo函數(shù)的內(nèi)存空間0x500,父級作用域為GO
- 將foo添加到GO對象中拓巧,值為0x500
-
ast --> Ignition
- 創(chuàng)建全局上下文斯碌,VO指向GO
- 執(zhí)行foo函數(shù)前,創(chuàng)建函數(shù)上下文肛度,VO對象指向AO對象
- 創(chuàng)建AO對象傻唾,將a添加到AO對象中,值為undefined
-
賦值
- 執(zhí)行foo函數(shù)承耿,var a = b = 10冠骄,相當于var a = 10; b = 10,a變量有標識符加袋,所以a被添加到AO對象中凛辣,賦值為10,b沒有表示符职烧,所以b被添加到全局對象GO扁誓,賦值為10
- 打印b防泵,GO對象中能找到b,值為10
- 打印a蝗敢,GO對象中沒有a择克,且沒有父級作用域,無法向上查找前普,此時報錯
所以執(zhí)行結果為
10
Uncaught ReferenceError: a is not defined
以上就是如何從javascript代碼解析過程理解執(zhí)行上下文與作用域提升的具體介紹肚邢,關于js高級,還有很多需要開發(fā)者掌握的地方拭卿,可以看看我寫的其他博文骡湖,持續(xù)更新中~