執(zhí)行上下文,就是Js執(zhí)行的時(shí)候的一個(gè)運(yùn)行環(huán)境/作用域(scope)始赎。執(zhí)行上下文決定了Js執(zhí)行過(guò)程中可以獲取哪些變量香璃、函數(shù)这难、數(shù)據(jù),一段程序可能被分割成許多不同的上下文葡秒,每一個(gè)上下文都會(huì)綁定一個(gè)變量對(duì)象(variable object)姻乓,它就像一個(gè)容器,用來(lái)存儲(chǔ)當(dāng)前上下文中所有已定義或可獲取的變量眯牧、函數(shù)等蹋岩。
可執(zhí)行代碼
既然執(zhí)行上下文是在JS執(zhí)行的時(shí)候創(chuàng)建的,那么JS中可執(zhí)行代碼的類型有:
- 全局代碼学少,這個(gè)是默認(rèn)的代碼運(yùn)行環(huán)境剪个,一旦代碼被載入,引擎最先進(jìn)入的就是這個(gè)環(huán)境版确;
- 函數(shù)級(jí)別的代碼扣囊,當(dāng)執(zhí)行一個(gè)函數(shù)時(shí),運(yùn)行函數(shù)體中的代碼绒疗;
- Eval的代碼侵歇,在Eval函數(shù)內(nèi)運(yùn)行的代碼;
執(zhí)行上下文特點(diǎn)
執(zhí)行上下文有以下特點(diǎn):
- 單線程吓蘑;
- 同步執(zhí)行惕虑,只有棧頂?shù)纳舷挛奶幱趫?zhí)行中,其他上下文需要等待磨镶;
- 唯一的一個(gè)全局上下文溃蔫,它在瀏覽器關(guān)閉時(shí)出棧;
- 函數(shù)的執(zhí)行上下文的個(gè)數(shù)沒(méi)有限制琳猫;
- 每次某個(gè)函數(shù)被調(diào)用伟叛,就會(huì)有個(gè)新的執(zhí)行上下文為其創(chuàng)建,即使是調(diào)用的自身函數(shù)脐嫂,也是如此痪伦;
執(zhí)行上下文的創(chuàng)建和執(zhí)行
- 上下文的創(chuàng)建階段:函數(shù)被調(diào)用侄榴,但尚未開(kāi)始執(zhí)行(代碼分析預(yù)處理階段),此時(shí)會(huì)為執(zhí)行上下文創(chuàng)建作用域鏈网沾,創(chuàng)建變量癞蚕、函數(shù)和參數(shù)以及求this的值。也就是發(fā)生聲明提升的階段辉哥,同時(shí)也是產(chǎn)生聲明提升的原因桦山;
- 執(zhí)行階段:指派變量的值和函數(shù)的引用并解釋執(zhí)行代碼,也就是變量賦值醋旦,函數(shù)引用恒水,執(zhí)行其它代碼,在執(zhí)行階段饲齐,JS引擎會(huì)創(chuàng)建執(zhí)行上下文棧來(lái)管理執(zhí)行上下文钉凌;
簡(jiǎn)單例子:
var a = 1; // ①
function f() { // ②
var a = 2; // ③
function sayA() { // ④
console.log(a); // ⑤
}
sayA(); // ⑥
}
f(); // ⑦
首先先將執(zhí)行上下文和執(zhí)行上下文棧具象化:
-
將每個(gè)執(zhí)行上下文看作一個(gè)對(duì)象,它包含三個(gè)屬性:
ECObj = { variableObject: {}, // 變量對(duì)象捂人,函數(shù)中的arguments對(duì)象, 參數(shù), 內(nèi)部的變量以及函數(shù)聲明 scopeChain: [], // 作用域鏈御雕,包含執(zhí)行上下文自身變量對(duì)象以及所有父執(zhí)行上下文中的變量對(duì)象,也就是所有可訪問(wèn)的變量和函數(shù) this: {} // this指向的對(duì)象 };
-
將執(zhí)行上下文椑拇睿看作一個(gè)數(shù)組酸纲;
ECStack = []; // 初始執(zhí)行上下文棧為空數(shù)組
上面代碼具體執(zhí)行情況:
-
代碼開(kāi)始執(zhí)行之前,首先創(chuàng)建全局執(zhí)行上下文瑟匆;
globalECObj = { variableObject: { f: pointer to function f(), // 函數(shù)聲明會(huì)指向該函數(shù)在內(nèi)存中的地址的一個(gè)引用闽坡,所以函數(shù)可以在聲明之前調(diào)用 a: undefined // 變量聲明初始化會(huì)是undefined }, scopeChain: Object.assign({}, globalECObj.variableObject), // 作用域鏈?zhǔn)侨肿饔糜? this: window }; // 執(zhí)行全局執(zhí)行上下文,全局執(zhí)行上下文入棧 ECStack.push(globalECObj);
-
全局執(zhí)行上下文創(chuàng)建完畢愁溜,開(kāi)始執(zhí)行代碼疾嗅,從上至下,① 為賦值操作冕象;
// ① var a = 1; 給全局變量a賦值 globalECObj = { variableObject: { f: pointer to function f(), a: 1 }, scopeChain: Object.assign({}, globalECObj.variableObject), this: window };
-
跳過(guò)函數(shù)聲明直到遇到可執(zhí)行到代碼 ⑦ 代承;
// ⑦ f(); 遇到函數(shù)調(diào)用,創(chuàng)建函數(shù)f的執(zhí)行上下文 fECObj = { variableObject: { sayA: pointer to function sayA(), a: undefined }, scopeChain: Object.assign({}, globalECObj.variableObject, fECObj.variableObject), // 作用域鏈?zhǔn)侨肿饔糜蚝蚮函數(shù)作用域 this: window }; // 函數(shù)f執(zhí)行上下文入棧 ECStack.push(fECObj); // 執(zhí)行f執(zhí)行上下文 // ③ var a = 2; 給局部變量a賦值 fECObj = { variableObject: { sayA: pointer to function sayA(), a: 2 }, scopeChain: Object.assign({}, globalECObj.variableObject, fECObj.variableObject), this: window }
-
執(zhí)行函數(shù)f執(zhí)行上下文中又遇到可執(zhí)行代碼 ⑥ 交惯;
// ⑥ sayA(); 遇到函數(shù)調(diào)用次泽,創(chuàng)建函數(shù)sayA執(zhí)行上下文 sayAECObj = { variableObject: {}, scopeChain: Object.assign({}, globalECObj.variableObject, fECObj.variableObject, sayAECObj.variableObject), // 作用域鏈?zhǔn)侨肿饔糜蚝蚮函數(shù)作用域和sayA函數(shù)作用域 this: window }; // 函數(shù)sayA執(zhí)行上下文入棧 ECStack.push(sayAECObj); // 執(zhí)行sayA執(zhí)行上下文 2 // 彈出結(jié)果2
-
sayA函數(shù)執(zhí)行結(jié)束穿仪,從執(zhí)行上下文棧彈出
ECStack.pop();
-
f函數(shù)執(zhí)行結(jié)束席爽,從執(zhí)行上下文棧彈出
ECStack.pop();
現(xiàn)在只剩全局執(zhí)行上下文了,它會(huì)在瀏覽器關(guān)閉從執(zhí)行上下文棧中彈出
執(zhí)行上下文過(guò)程
- JS代碼加載完成啊片;
- 創(chuàng)建全局執(zhí)行上下文只锻,為全局執(zhí)行上下文創(chuàng)建作用域鏈,創(chuàng)建變量紫谷、函數(shù)和參數(shù)以及求this的值齐饮;
- 全局執(zhí)行上下文入執(zhí)行上下文棧捐寥,開(kāi)始執(zhí)行代碼;
- JS為單線程祖驱,從上至下當(dāng)遇到可執(zhí)行代碼握恳,就新建一個(gè)執(zhí)行上下文,為新建執(zhí)行上下文創(chuàng)建作用域鏈捺僻,創(chuàng)建變量乡洼、函數(shù)和參數(shù)以及求this的值;
- 新建執(zhí)行上下文入執(zhí)行上下文棧匕坯,開(kāi)始執(zhí)行代碼束昵;
- 如果可執(zhí)行代碼中還有可執(zhí)行代碼就重復(fù)4和5;
- 執(zhí)行完成的執(zhí)行上下文從執(zhí)行上下文棧頂彈出葛峻;
- 關(guān)閉瀏覽器后全局執(zhí)行上下文從執(zhí)行上下文棧中彈出锹雏,執(zhí)行上下文棧清空;