在我們前面理解了作用域之后汇荐,“作用域鏈”這個(gè)概念就產(chǎn)生了携冤。那么作用域鏈?zhǔn)鞘裁匆馑迹质窃趺葱纬傻闹让男└拍钣嘘P(guān)系砚亭,這就是我接下來幾章想和大家探討的內(nèi)容:執(zhí)行上下文、變量對(duì)象和作用域鏈殴玛。根據(jù)順序我們也可以看出來捅膘,想要理解作用域鏈,執(zhí)行上下文是我們碰到的第一個(gè)坎滚粟。
這一章我們就來討論一下到底什么是執(zhí)行上下文篓跛。
1. 定義
當(dāng) JS 引擎開始執(zhí)行預(yù)編譯生成的代碼時(shí),就會(huì)進(jìn)入到一個(gè)執(zhí)行上下文(Executable Code - 簡稱 EC)坦刀。
在 ECMA 標(biāo)準(zhǔn)規(guī)范里并沒有從技術(shù)角度去定義 EC 的具體類型和結(jié)構(gòu)愧沟,這個(gè)是在實(shí)現(xiàn) ECMAScript 引擎時(shí)需要考慮的問題。
但是在邏輯上鲤遥,我們可以將活動(dòng)的執(zhí)行上下文看成一個(gè)棧結(jié)構(gòu)沐寺。棧底部永遠(yuǎn)是全局上下文(global context),而頂部就是當(dāng)前活動(dòng)的執(zhí)行上下文盖奈。執(zhí)行到當(dāng)前代碼時(shí)混坞,上下文入棧,執(zhí)行完畢后钢坦,上下文出棧究孕。
2. 可執(zhí)行代碼有幾種
前面說到當(dāng)引擎執(zhí)行到可執(zhí)行代碼的時(shí)候,就會(huì)將當(dāng)前上下文壓入上下文棧中爹凹。那么可執(zhí)行的代碼又分為幾種厨诸?
在這里,我們先假設(shè)定義執(zhí)行上下文棧是一個(gè)數(shù)組:
EC = [];
第一種可執(zhí)行代碼 -- 全局代碼:
全局類型代碼是在加載外部的 js 文件或者本地 <script></script> 標(biāo)簽中的代碼禾酱。
注意微酬,在全局代碼中,并不包含定義在全局環(huán)境 function 內(nèi)的代碼颤陶。
程序啟動(dòng)后進(jìn)入初始化全局環(huán)境:
EC = [
globalContext
];
第二種可執(zhí)行代碼 -- 函數(shù)代碼:
當(dāng)定義的函數(shù)被執(zhí)行時(shí)颗管,就進(jìn)入了函數(shù)代碼,當(dāng)前函數(shù)上下文被壓入 EC 棧中滓走。
注意垦江,在函數(shù)代碼中,也不包含定義在該函數(shù)內(nèi)部環(huán)境 function 內(nèi)的代碼搅方。
例如:
var a = 10;
function foo () {
var b = 20;
foo();
}
foo();
這個(gè)例子中的 EC 是什么樣子的呢比吭?
// 初始化
EC = [
globalContext
];
// 第一次調(diào)用 foo 函數(shù)
EC = [
<foo> functionContext,
globalContext
];
// 在 foo 內(nèi)遞歸調(diào)用自己
EC = [
<foo> functionContext - recursively,
<foo> functionContext,
globalContext
];
// 繼續(xù)遞歸調(diào)用自己
EC = [
......
<foo> functionContext - recursively2,
<foo> functionContext - recursively,
<foo> functionContext,
globalContext
];
// 遞歸會(huì)不斷調(diào)用下去绽族,因?yàn)闆]有結(jié)束條件,所以這是一個(gè)死循環(huán)
// 所以梗逮,EC 只會(huì)不斷增加新的上下文项秉,但是卻不會(huì)退出
只有每次 return 的時(shí)候,才會(huì)退出當(dāng)前執(zhí)行上下文慷彤,相應(yīng)上下文會(huì)從棧中彈出娄蔼,棧指針會(huì)自動(dòng)移動(dòng)位置。
注意底哗,當(dāng)函數(shù)沒有明確指明 return 什么的時(shí)候岁诉,默認(rèn) return undefined 。
如果有拋出的異常沒有被截獲的話跋选,也有可能從一個(gè)或多個(gè)執(zhí)行上下文中退出涕癣。當(dāng)所有代碼執(zhí)行完以后,EC 中只會(huì)包含全局上下文(global context)前标,當(dāng)程序退出以后坠韩,全局上下文也會(huì)退出。
第三種可執(zhí)行代碼 -- eval 代碼:
eval 函數(shù)在調(diào)用的時(shí)候會(huì)產(chǎn)生上下文炼列。
例如:
eval('var a = 10');
(function foo () {
eval('var b = 20');
}());
alert(a); // 10
alert(b); // ReferenceError只搁,b is not defined
這個(gè)例子中 EC 的變化如下:
// 初始化
EC = [
globalContext
];
// eval('var a = 10');
EC = [
evalContext,
globalContext
];
// eval 執(zhí)行完畢
EC = [
globalContext
];
// 立即執(zhí)行函數(shù) foo
EC = [
<foo> functionContext,
globalContext
];
// eval('var b = 20');
EC = [
evalContext,
<foo> functionContext,
globalContext
];
// eval 執(zhí)行完畢
EC = [
<foo> functionContext,
globalContext
];
// foo 執(zhí)行完畢
EC = [
globalContext
];
這就是一個(gè)典型的邏輯調(diào)用上下文棧。
在 setTimeout 和 setInterval 函數(shù)中的第一個(gè)參數(shù)也可以傳入代碼字符串俭尖,但是這個(gè)一般不會(huì)這么去用氢惋,所以這里也就不討論了。
3. 結(jié)論
執(zhí)行上下文環(huán)境是我們了解變量對(duì)象和作用域鏈的基礎(chǔ)稽犁,大家一定要好好理解(其實(shí)也并不難)焰望,下一節(jié)我們來討論變量對(duì)象,相信會(huì)讓大家有一定的收獲已亥。