執行上下文,就是Js執行的時候的一個運行環境/作用域(scope)。執行上下文決定了Js執行過程中可以獲取哪些變量、函數、數據,一段程序可能被分割成許多不同的上下文,每一個上下文都會綁定一個變量對象(variable object),它就像一個容器,用來存儲當前上下文中所有已定義或可獲取的變量、函數等。
可執行代碼
既然執行上下文是在JS執行的時候創建的,那么JS中可執行代碼的類型有:
- 全局代碼,這個是默認的代碼運行環境,一旦代碼被載入,引擎最先進入的就是這個環境;
- 函數級別的代碼,當執行一個函數時,運行函數體中的代碼;
- Eval的代碼,在Eval函數內運行的代碼;
執行上下文特點
執行上下文有以下特點:
- 單線程;
- 同步執行,只有棧頂的上下文處于執行中,其他上下文需要等待;
- 唯一的一個全局上下文,它在瀏覽器關閉時出棧;
- 函數的執行上下文的個數沒有限制;
- 每次某個函數被調用,就會有個新的執行上下文為其創建,即使是調用的自身函數,也是如此;
執行上下文的創建和執行
- 上下文的創建階段:函數被調用,但尚未開始執行(代碼分析預處理階段),此時會為執行上下文創建作用域鏈,創建變量、函數和參數以及求this的值。也就是發生聲明提升的階段,同時也是產生聲明提升的原因;
- 執行階段:指派變量的值和函數的引用并解釋執行代碼,也就是變量賦值,函數引用,執行其它代碼,在執行階段,JS引擎會創建執行上下文棧來管理執行上下文;
簡單例子:
var a = 1; // ①
function f() { // ②
var a = 2; // ③
function sayA() { // ④
console.log(a); // ⑤
}
sayA(); // ⑥
}
f(); // ⑦
首先先將執行上下文和執行上下文棧具象化:
-
將每個執行上下文看作一個對象,它包含三個屬性:
ECObj = { variableObject: {}, // 變量對象,函數中的arguments對象, 參數, 內部的變量以及函數聲明 scopeChain: [], // 作用域鏈,包含執行上下文自身變量對象以及所有父執行上下文中的變量對象,也就是所有可訪問的變量和函數 this: {} // this指向的對象 };
-
將執行上下文??醋饕粋€數組;
ECStack = []; // 初始執行上下文棧為空數組
上面代碼具體執行情況:
-
代碼開始執行之前,首先創建全局執行上下文;
globalECObj = { variableObject: { f: pointer to function f(), // 函數聲明會指向該函數在內存中的地址的一個引用,所以函數可以在聲明之前調用 a: undefined // 變量聲明初始化會是undefined }, scopeChain: Object.assign({}, globalECObj.variableObject), // 作用域鏈是全局作用域 this: window }; // 執行全局執行上下文,全局執行上下文入棧 ECStack.push(globalECObj);
-
全局執行上下文創建完畢,開始執行代碼,從上至下,① 為賦值操作;
// ① var a = 1; 給全局變量a賦值 globalECObj = { variableObject: { f: pointer to function f(), a: 1 }, scopeChain: Object.assign({}, globalECObj.variableObject), this: window };
-
跳過函數聲明直到遇到可執行到代碼 ⑦ ;
// ⑦ f(); 遇到函數調用,創建函數f的執行上下文 fECObj = { variableObject: { sayA: pointer to function sayA(), a: undefined }, scopeChain: Object.assign({}, globalECObj.variableObject, fECObj.variableObject), // 作用域鏈是全局作用域和f函數作用域 this: window }; // 函數f執行上下文入棧 ECStack.push(fECObj); // 執行f執行上下文 // ③ var a = 2; 給局部變量a賦值 fECObj = { variableObject: { sayA: pointer to function sayA(), a: 2 }, scopeChain: Object.assign({}, globalECObj.variableObject, fECObj.variableObject), this: window }
-
執行函數f執行上下文中又遇到可執行代碼 ⑥ ;
// ⑥ sayA(); 遇到函數調用,創建函數sayA執行上下文 sayAECObj = { variableObject: {}, scopeChain: Object.assign({}, globalECObj.variableObject, fECObj.variableObject, sayAECObj.variableObject), // 作用域鏈是全局作用域和f函數作用域和sayA函數作用域 this: window }; // 函數sayA執行上下文入棧 ECStack.push(sayAECObj); // 執行sayA執行上下文 2 // 彈出結果2
-
sayA函數執行結束,從執行上下文棧彈出
ECStack.pop();
-
f函數執行結束,從執行上下文棧彈出
ECStack.pop();
現在只剩全局執行上下文了,它會在瀏覽器關閉從執行上下文棧中彈出
執行上下文過程
- JS代碼加載完成;
- 創建全局執行上下文,為全局執行上下文創建作用域鏈,創建變量、函數和參數以及求this的值;
- 全局執行上下文入執行上下文棧,開始執行代碼;
- JS為單線程,從上至下當遇到可執行代碼,就新建一個執行上下文,為新建執行上下文創建作用域鏈,創建變量、函數和參數以及求this的值;
- 新建執行上下文入執行上下文棧,開始執行代碼;
- 如果可執行代碼中還有可執行代碼就重復4和5;
- 執行完成的執行上下文從執行上下文棧頂彈出;
- 關閉瀏覽器后全局執行上下文從執行上下文棧中彈出,執行上下文棧清空;