一段javaScript代碼的編譯執(zhí)行過程

javaScript代碼在執(zhí)行前會經(jīng)過一個耗時極短的編譯過程噩峦,并在編譯完成后立即運行秀睛。

我們以一段代碼為例币他,看看這個過程的一些細節(jié):

var a = 1;
function fun1 (b) {
    var c = 2;
    return c + b + a;
}
var sum = fun1(3);

我們暫時不關(guān)注編譯器詞法分析坞靶、語法分析代碼生成優(yōu)化的部分圆丹,而是看一看這一過程中上下文做了哪些事。

一躯喇、全局上下文

1.建立一個空的全局上下文

編譯器首先會創(chuàng)建一個全局上下文辫封,我們叫它global_context吧。全局上下文格式大致如下:

global_context: {
    Variable_Object: {}, // 當前上下文的變量對象
    Scope: [global_context.Variable_Object], // 當前上下文的作用域鏈
    this: {} // this對象
}

[注1]Scope的值如何攘觥:

  • 全局上下文: global_context.Variable_Object,也就是全局上下文的VO對象
  • 執(zhí)行上下文:execute_context.Scope = execute_context.Variable_Object + global_function1.scope,也就是把當前執(zhí)行上下文的VO對象添加到執(zhí)行的函數(shù)的scope數(shù)組第一位

[注2]上下文對象的結(jié)構(gòu)僅為偽代碼倦微,并非實際結(jié)構(gòu)。同時VO與存儲直接的交互細節(jié)我們也不考慮正压。
[注3]:本文中我們暫時不考慮this的細節(jié)欣福。

2.上下文入棧

將當前的上下文global_context壓入一個棧中,我們可以稱這個棧為context_stack焦履。如圖所示:

全局上下文入棧

3.獲取當前上下文變量對象(預(yù)處理)

在這一階段拓劝,實際上發(fā)生的是JavaScript聲明提升過程雏逾,聲明提升詳情見JavaScript聲明提升
所以我們先將變量的聲明提升郑临,也就是將所有聲明的變量存放在global_context.Variable_ObjectVO)中:

編譯器看到var a = 1;這一句栖博,就查詢當前上下文(global_context)有沒有這個變量,也就是看看Variable_Object(簡稱VO)里面有沒有變量a厢洞。發(fā)現(xiàn)沒有仇让,就將a加進去,值為undefined躺翻,如果有丧叽,就重新賦值:

global_context: {
    Variable_Object: {
        a: undefined
    },
    Scope: [global_context.Variable_Object],
    this: {}
}

之后,繼續(xù)將全局變量var sum加入全局上下文的VO:

global_context: {
    Variable_Object: {
        a: undefined,
        sum: undefined
    },
    Scope: [global_context.Variable_Object],
    this: {}
}

接著公你,將函數(shù)聲明提升:
編譯器看到function fun1踊淳,判斷這是一個函數(shù)聲明,將函數(shù)聲明也加到VO省店,值為函數(shù)的地址嚣崭。同時,還會在給這個函數(shù)對象加一個屬性scope懦傍,值為當前上下文的Scope值:

global_context: {
    Variable_Object: {
        a: undefined,
        fun1:{
            函數(shù) global_function1的地址,
            scope: [global_context.Variable_Object]
        },
        sum: undefined
    },
    Scope: [global_context.Variable_Object],
    this: {}
}

4.執(zhí)行代碼

經(jīng)過編譯后雹舀,在全局上下文中執(zhí)行的代碼實際上是以下代碼:

a = 1;
sum = fun1(3);

我們一句一句執(zhí)行:

a = 1;實際上就是查詢當前上下文的VO中有沒有變量a,如果有,就給a賦值1:

global_context: {
    Variable_Object: {
        a: 1,
        fun1:{
            函數(shù) global_function1的地址,
            scope: [global_context.Variable_Object]
        },
        sum: undefined
    },
    Scope: [global_context.Variable_Object],
    this: {}
}

sum = fun1(3);同理粗俱,先看右邊说榆,找到變量fun1()說明這個函數(shù)需要立即執(zhí)行寸认,我們執(zhí)行這個函數(shù)签财,然后把結(jié)果賦值給變量sum

二偏塞、執(zhí)行上下文

編譯器并沒有立即執(zhí)行函數(shù)fun1中的代碼唱蒸,因為它要為函數(shù)創(chuàng)建一個專門的context,我們叫它執(zhí)行上下文(execute_context)吧灸叼,因為每當編譯器要執(zhí)行一個函數(shù)時神汹,都會創(chuàng)建一個類似的context

1.建立一個空的執(zhí)行上下文

execute_context也是一個對象古今,并且與global_context還很像屁魏,下面是它里面的內(nèi)容:

execute_context: {
       Variable_Object: {},
       Scope: [execute_context.Variable_Object, global_context.Variable_Object ],
       this: {}
}

注意:當前執(zhí)行上下文的Scope如何取值:
當前上下文時函數(shù)fun1的執(zhí)行上下文,我們先取fun1.scope也就是[global_context.Variable_Object]捉腥,
然后將當前上下文的VO也就是execute_context.Variable_Object添加到[global_context.Variable_Object]的第一位氓拼,變成了[execute_context.Variable_Object, global_context.Variable_Object ]

2.執(zhí)行上下文入棧

執(zhí)行上下文入棧

3.預(yù)處理

對與代碼

function fun1 (b) {
    var c = 2;
    return c + b + a;
}

我們直接分析對這段代碼進行聲明提升后的偽代碼

function fun1 (var b) {
    var c;
    b = 參數(shù);
    c = 2;
    返回值 = c + b + a;
    return 返回值;
}

對與變量聲明,我們在當前上下文的VO中存放這些對象:

execute_context: {
        Variable_Object: {
            b: undefined,
            c: undefined,
            arguments: [] // 形參列表
        },
        Scope: [execute_context.Variable_Object, global_context.Variable_Object ],
        this: {}
}

[注4]arguments對象是一個用于存放形參的數(shù)組桃漾,例如我們這樣調(diào)用函數(shù)fun1(1,2,3)坏匪,這時就需要用arguments來存放我們未寫明形參的值了。執(zhí)行時呈队,arguments=[1,2,3]剥槐。

4.執(zhí)行

實際需要執(zhí)行的偽代碼為:

b = 3;
c = 2;
返回值 = c + b + a;
return 返回值;
作用域鏈:

這里我們討論一下執(zhí)行時,如何判斷變量合法(在作用域中)

例如b = 3宪摧,這是一個賦值操作粒竖,我們需要確定b這個變量我們有聲明過,并找到它几于,這樣才能給它賦值蕊苗。這時就需要用到當前上下文的Scope對象了,我們?nèi)〉?br> execute_context.Scope = [execute_context.Variable_Object, global_context.Variable_Object ]

這時沿彭,我們要找一個叫b的變量朽砰,首先找execute_context.Scope的數(shù)組第一項execute_context.Variable_Object,看看有沒有喉刘,這里我們之間就找到了瞧柔。

如果找變量a呢?我們發(fā)現(xiàn)execute_context.Variable_Object里面沒有定義變量a睦裳,這時我們找execute_context.Scope的數(shù)組第二項造锅,如果還沒找到,以此類推廉邑,找完execute_context.Scope的所有值為止哥蔚,這個時候,如果還沒找到蛛蒙,我們就認為這個變量在作用域內(nèi)未定義糙箍。
至于未定義后的處理:非嚴格模式下回再全局上下文的VO里面添加一個這個變量,嚴格模式則直接報錯not defined牵祟。

當然深夯,這里我們在第二項global_context.Variable_Object里面找到了變量a。它是一個全局變量诺苹。

因此:當前上下文的scope對象記錄了由里到外注冊(聲明)的所有變量和函數(shù)咕晋,因此稱為作用域鏈。

對于賦值操作b= 3; c = 2;筝尾,我們在當前上下文的VO中找到了它們捡需,直接賦值即可:

execute_context: {
        Variable_Object: {
            b: 3,
            c: 2,
            arguments: [3] // 形參列表
        },
        Scope: [execute_context.Variable_Object, global_context.Variable_Object ],
        this: {}
}

對于c+ b + a办桨,我們在當前上下文VO找到了cb筹淫,在全局上下文找到了a,將三個數(shù)值相加,返回三個變量的和2 + 3 + 1 = 6损姜。

5.執(zhí)行上下文出棧

當前代碼塊執(zhí)行完畢后饰剥,它對應(yīng)的上下文就會出棧,也就更改了當前的上下文摧阅。
例如汰蓉,函數(shù)fun1執(zhí)行完畢后,它的上下文execute_context出棧棒卷,當前上下文變成了全局上下文global_context顾孽。
全局上下文出棧代表著所有代碼運行完畢。

執(zhí)行上下文出棧

三比规、全局上下文

我們繼續(xù)運行全局上下文若厚,之前運行到sum = fun1(3);,我們運行完畢了函數(shù)fun1(3)蜒什,并得到了結(jié)果6测秸。
所以,這一句就變成了sum =6;的賦值語句灾常,我們在全局上下文的VO找到了這個變量sum霎冯,直接給它賦值就好了:

global_context: {
    Variable_Object: {
        a: undefined,
        fun1:{
            函數(shù) global_function1的地址,
            scope: [global_context.Variable_Object]
        },
        sum: 6
    },
    Scope: [global_context.Variable_Object],
    this: {}
}

此時,后面沒有要執(zhí)行的代碼了钞瀑,全局上下文出棧沈撞,結(jié)束代碼運行。

參考文獻:

教你步步為營掌握JavaScript閉包

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仔戈,一起剝皮案震驚了整個濱河市关串,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌监徘,老刑警劉巖晋修,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凰盔,居然都是意外死亡墓卦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門户敬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來落剪,“玉大人,你說我怎么就攤上這事尿庐≈也溃” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵抄瑟,是天一觀的道長凡泣。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么鞋拟? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任骂维,我火速辦了婚禮,結(jié)果婚禮上贺纲,老公的妹妹穿的比我還像新娘航闺。我一直安慰自己,他們只是感情好猴誊,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布潦刃。 她就那樣靜靜地躺著,像睡著了一般懈叹。 火紅的嫁衣襯著肌膚如雪福铅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天项阴,我揣著相機與錄音滑黔,去河邊找鬼。 笑死环揽,一個胖子當著我的面吹牛略荡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播歉胶,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼汛兜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了通今?” 一聲冷哼從身側(cè)響起粥谬,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辫塌,沒想到半個月后漏策,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡臼氨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年掺喻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片储矩。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡感耙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出持隧,到底是詐尸還是另有隱情即硼,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布屡拨,位于F島的核電站只酥,受9級特大地震影響题诵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜层皱,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赠潦。 院中可真熱鬧叫胖,春花似錦、人聲如沸她奥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哩俭。三九已至绷跑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凡资,已是汗流浹背砸捏。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留隙赁,地道東北人垦藏。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像伞访,于是被迫代替她去往敵國和親掂骏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345