從javascript代碼解析過程理解執(zhí)行上下文與作用域提升

javascript代碼解析過程

執(zhí)行上下文和作用域是javascript中非常重要的部分渤早,要弄清楚它們首先就要說到javascript的運行機制谤专,javascript代碼被解析經(jīng)過了以下幾個步驟

  • Parser模塊將javascript源碼解析成抽象語法樹(AST)
  • Ignition模塊將抽象語法樹編譯成字節(jié)碼(byteCode)训措,再編譯成機器碼
  • 當函數(shù)被執(zhí)行多次時郑临,Ignition會記錄優(yōu)化信息颜骤,由Turbofan直接將抽象語法樹編譯成機器碼
v8引擎的工作過程.png

全局上下文

了解完以上javascript運行機制之后聂渊,我們來看看以下全局代碼的執(zhí)行方式

console.log(user)
var user = 'alice' 
var num = 16
console.log(num)

以上代碼經(jīng)過如下步驟才被執(zhí)行

  1. javascript --> ast

    • 全局創(chuàng)建一個GO( GlobalObject)對象孝宗,GO中有很多內(nèi)置模塊穷躁,如Math、String、以及window屬性问潭,其中window的值為this猿诸,也就是自身GO對象
    • 全局定義的變量user和num會添加GO對象中,并賦值為undefined
  2. ast --> Ignition

    • V8引擎執(zhí)行代碼時狡忙,存在調(diào)用棧(ECStack)梳虽,此時創(chuàng)建全局上下文棧(Global Excution Context)
    • GEC存在VO(variable Object),在全局上下文中指向GO對象
  3. Ignition --> 運行結果

    • 通過VO找到GO
    • 將user賦值為alice灾茁,將num賦值為16

圖示如下


全局上下文執(zhí)行.png

以上代碼的執(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í)行

  1. 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
  2. ast --> Ignition

    • V8引擎執(zhí)行代碼時举瑰,存在調(diào)用棧(ECStack)捣辆,此時創(chuàng)建全局上下文棧(Global Excution Context)
    • GEC存在VO(variable Object),在全局上下文中指向GO對象
  3. 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)棧頂彈出

圖示如下


函數(shù)上下文執(zhí)行.png

所以上面代碼執(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


v8解析過程.png

閉包的執(zhí)行順序

var user = "alice"

foo(12) 
function foo(num){ 
    console.log(m)
    var m = 10
    
    function bar(){ 
        console.log(user)
    } 
    bar() 
} 

以上代碼經(jīng)過如下步驟才被執(zhí)行

  1. 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
  2. ast --> Ignition

    • V8引擎執(zhí)行代碼時脚乡,存在調(diào)用棧(ECStack),此時創(chuàng)建全局上下文棧(Global Excution Context)
    • GEC存在VO(variable Object)滨达,在全局上下文中指向GO對象
  3. 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對象中


    函數(shù)中存在函數(shù).png

作用域

作用域是在解析成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í)行

  1. 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
  2. ast --> Ignition

    • V8引擎執(zhí)行代碼時庸娱,存在調(diào)用棧(ECStack),此時創(chuàng)建全局上下文棧(Global Excution Context)
    • GEC存在VO(variable Object)谐算,在全局上下文中指向GO對象
  3. 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

圖示如下


作用域提升.png

易混淆點

一、 沒有通過var標識符聲明的變量會被添加到全局

var n = 100 

function foo(){ 
    n = 200
} 

foo()
console.log(n)

執(zhí)行過程如下

  1. javascript --> ast

    • GO對象中將n定義為undefined
    • 開辟foo函數(shù)的內(nèi)存空間0x100潘飘,父級作用域為GO
    • 將foo添加到GO對象中肮之,值為0x100
  2. ast --> Ignition

    • 創(chuàng)建全局上下文,VO指向GO
    • 執(zhí)行foo函數(shù)前福也,創(chuàng)建函數(shù)上下文局骤,VO對象指向AO對象
    • 創(chuàng)建AO對象攀圈,AO為空對象
  3. 賦值

    • 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í)行順序如下

  1. javascript --> ast

    • GO對象中添加變量n,值為undefined
    • 為函數(shù)foo開辟內(nèi)存空間0x300犬辰,父級作用域為GO
    • 將foo添加到GO對象中嗦篱,值為0x300
  2. ast ---> Ignition

    • 創(chuàng)建全局上下文,VO指向GO
    • 執(zhí)行函數(shù)foo之前創(chuàng)建函數(shù)上下文幌缝,VO指向AO
    • 創(chuàng)建AO對象灸促,添加變量n,值為undefined
  3. 賦值

    • 將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í)行過程如下

  1. javascript --> ast

    • GO對象中將a定義為undefined
    • 開辟foo函數(shù)的內(nèi)存空間0x400球碉,父級作用域為GO
    • 將foo添加到GO對象中蜓斧,值為0x400
  2. ast --> Ignition

    • 創(chuàng)建全局上下文,VO指向GO
    • 執(zhí)行foo函數(shù)前睁冬,創(chuàng)建函數(shù)上下文法精,VO對象指向AO對象
    • 創(chuàng)建AO對象,將a添加到AO對象中痴突,值為undefined
  3. 賦值

    • 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í)行過程如下

  1. javascript --> ast

    • 創(chuàng)建GO對象殉挽,GO對象為空
    • 開辟foo函數(shù)的內(nèi)存空間0x500,父級作用域為GO
    • 將foo添加到GO對象中拓巧,值為0x500
  2. ast --> Ignition

    • 創(chuàng)建全局上下文斯碌,VO指向GO
    • 執(zhí)行foo函數(shù)前,創(chuàng)建函數(shù)上下文肛度,VO對象指向AO對象
    • 創(chuàng)建AO對象傻唾,將a添加到AO對象中,值為undefined
  3. 賦值

    • 執(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ù)更新中~

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市峻厚,隨后出現(xiàn)的幾起案子响蕴,更是在濱河造成了極大的恐慌,老刑警劉巖惠桃,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浦夷,死亡現(xiàn)場離奇詭異,居然都是意外死亡辜王,警方通過查閱死者的電腦和手機劈狐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呐馆,“玉大人肥缔,你說我怎么就攤上這事⌒诶矗” “怎么了续膳?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長收班。 經(jīng)常有香客問我坟岔,道長,這世上最難降的妖魔是什么摔桦? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任社付,我火速辦了婚禮,結果婚禮上酣溃,老公的妹妹穿的比我還像新娘瘦穆。我一直安慰自己,他們只是感情好赊豌,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绵咱,像睡著了一般碘饼。 火紅的嫁衣襯著肌膚如雪熙兔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天艾恼,我揣著相機與錄音住涉,去河邊找鬼。 笑死钠绍,一個胖子當著我的面吹牛舆声,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播柳爽,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼媳握,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了磷脯?” 一聲冷哼從身側響起蛾找,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赵誓,沒想到半個月后打毛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡俩功,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年幻枉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诡蜓。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡展辞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出万牺,到底是詐尸還是另有隱情罗珍,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布脚粟,位于F島的核電站覆旱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏核无。R本人自食惡果不足惜扣唱,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望团南。 院中可真熱鬧噪沙,春花似錦、人聲如沸吐根。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拷橘。三九已至局义,卻和暖如春喜爷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萄唇。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工檩帐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人另萤。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓湃密,卻偏偏與公主長得像,于是被迫代替她去往敵國和親四敞。 傳聞我的和親對象是個殘疾皇子泛源,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容