執行上下文是評估和執行 JavaScript 代碼的環境的抽象概念。Javascript 代碼都是在執行上下文中運行。javascript
JavaScript 的可執行代碼(executable code)的類型只有三種,全局代碼、函數代碼、eval代碼。前端
對應着,JavaScript 中有三種執行上下文類型。java
this
的值等於這個全局對象。一個程序中只會有一個全局執行上下文。eval
函數內部的代碼也會有它屬於本身的執行上下文舉個栗子,當執行到一個函數的時候,就會進行準備工做,這裏的「準備工做」,就是準備"執行上下文(execution context)"。git
執行棧,是一種擁有 LIFO(後進先出)數據結構的棧,被用來存儲代碼運行時建立的全部執行上下文。github
當 JavaScript 開始要解釋執行代碼的時候,它會建立一個全局的執行上下文而且壓入當前執行棧。每當引擎遇到一個函數調用,它會爲該函數建立一個新的執行上下文並壓入棧的頂部。面試
程序結束以前, 執行棧最底部永遠有個全局上下文瀏覽器
引擎會執行那些執行上下文位於棧頂的函數。當該函數執行結束時,執行上下文從棧中彈出,控制流程到達當前棧中的下一個上下文。數據結構
模擬js執行如下代碼:函數
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
複製代碼
定義執行上下文棧:ECStack = [];
post
ECStack.push(globalContext);
ECStack.push(fun1Context);
ECStack.push(fun2Context);
ECStack.push(fun3Context);
ECStack.pop();
ECStack.pop();
ECStack.pop();
建立執行上下文有兩個階段:1) 建立階段 和 2) 執行階段。
或者你也能夠簡單理解爲:
因此執行上下文在概念上表示以下:
ExecutionContext = {
ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, } 複製代碼
在函數執行上下文中,this
的值取決於該函數是如何被調用的。若是它被一個引用對象調用,那麼 this
會被設置成那個對象,不然 this
的值被設置爲全局對象或者 undefined(嚴格模式下)
。
詞法環境對象
詞法環境和變量環境組件始終爲 詞法環境對象。
變量環境也是一個詞法環境,它有着詞法環境的全部屬性。
在 ES6 中,詞法環境組件和變量環境的一個不一樣就是前者被用來和變量(let
和 const
)綁定,然後者用來存儲函數聲明和 var
變量綁定。即:
每一個詞法環境對象包含兩部分:
如下面代碼爲例:
let a = 1;
const b = 2;
var c = 3;
function test (d, e) {
var f = 10;
return f * d * e;
}
c = test(a, b);
複製代碼
解析階段的全局環境內的詞法環境和變量環境
GlobalLexicalEnvironment = {
LexicalEnvironment: { // 詞法環境組件
OuterReference: null, // 全局詞法環境中外部引用爲空
EnviromentRecord: {
Type: 'object',
a: <uninitialized> , // let 和 const 變量綁定但未關聯值
b: <uninitialized>
},
},
VariableEnvironment: { //變量環境組件
EnviromentRecord: {
type: 'object',
test: <func>,
c: undefined, // var變量會被初始爲 undefined
}
}
}
複製代碼
解析test時的詞法環境和變量環境
注意:只有調用函數時,函數執行上下文才會被建立
// 此時 全局上下文已經執行,所以 a、b、c都已經與對應值關聯
GlobalLexicalEnvironment = {
LexicalEnvironment: {
OuterReference: null,
EnviromentRecord: {
Type: 'object',
a: 1 ,
b: 2
},
},
VariableEnvironment: {
EnviromentRecord: {
type: 'object',
c: 3,,
test: <func>
}
}
}
// test的詞法執行上下文開始構建,var變量綁定但未賦值,形參綁定
FunctionLexicalEnvironment = {
LexicalEnvironment: {
OuterReference: <GlobalLexicalEnvironment>,
EnviromentRecord: {
Type: 'Declarative',
arguments: {0: 1, 1: 2, length: 2}
},
},
VariableEnvironment: {
EnviromentRecord: {
Type: 'Declarative',
f: undefined,
}
}
}
複製代碼
插播一條變量提高的知識點:
在建立執行上下文時,js引擎會檢查當前做用域的全部變量聲明及函數聲明,在執行以前,var聲明的變量已經綁定初始undefined,而在let和const只綁定在了執行上下文中,但並未初始任何值,因此在聲明以前調用則會拋出引用錯誤(即TDZ暫時性死區),這也就是函數聲明與var聲明在執行上下文中的提高。
let/const也存在變量提高現象,詳情移至你可能不知道的變量提高
在執行上下文的建立階段,完成了變量聲明,在代碼的執行階段,纔會完成對變量真正的賦值。
在執行階段,若是 JavaScript 引擎不能在源碼中聲明變量的實際位置找到
let
變量的值,它會被賦值爲undefined
。
最後,看一個《JavaScript權威指南》中的例子:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
複製代碼
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
複製代碼
兩段代碼執行的結果同樣,都是
local scope,
若不理解,請移步詞法做用域及做用域鏈講解
可是兩段代碼究竟有哪些不一樣呢?
模擬第一段代碼:
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
複製代碼
模擬第二段代碼:
ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
複製代碼
ps: 這篇文章寫的很困難,蒐集資料的時候被各類詞語及講解弄的很懵,有些邏輯還有衝突,考慮了好久才決定只寫這些內容,將這篇文章只做爲對執行上下文的簡單描述而不是詳細講解,由於再寫多了,一些概念會使文章很難被閱讀和理解,等後續我有了深刻的理解再更新內容吧。若是有錯誤之處,歡迎在評論中指出~
相關係列: 從零開始的前端築基之旅(面試必備,持續更新~)
若是你收穫了新知識,請給做者點個贊吧,讓更多的人看到它~
參考文章:
- ****JavaScript深刻之執行上下文棧****
- ****[譯] 理解 JavaScript 中的執行上下文和執行棧****
- ****也來談談JS的執行上下文與詞法環境****