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_Object
(VO
)中:
編譯器看到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í)行上下文入棧
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
找到了c
和b
筹淫,在全局上下文找到了a
,將三個數(shù)值相加,返回三個變量的和2 + 3 + 1 = 6
损姜。
5.執(zhí)行上下文出棧
當前代碼塊執(zhí)行完畢后饰剥,它對應(yīng)的上下文就會出棧,也就更改了當前的上下文摧阅。
例如汰蓉,函數(shù)fun1
執(zhí)行完畢后,它的上下文execute_context
出棧棒卷,當前上下文變成了全局上下文global_context
顾孽。
全局上下文出棧代表著所有代碼運行完畢。
三比规、全局上下文
我們繼續(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é)束代碼運行。