一、執行上下文 簡單的來說,執行上下文是一種對Javascript代碼執行環境的抽象概念,也就是說只要有Javascript代碼運行,那麼它就一定是運行在執行上下文中 執行上下文的類型分為三種: 全局執行上下文:只有一個,瀏覽器中的全局對象就是 window對象,this 指向這個全局對象 函數執行 ...
一、執行上下文
簡單的來說,執行上下文是一種對Javascript
代碼執行環境的抽象概念,也就是說只要有Javascript
代碼運行,那麼它就一定是運行在執行上下文中
執行上下文的類型分為三種:
- 全局執行上下文:只有一個,瀏覽器中的全局對象就是
window
對象,this
指向這個全局對象 - 函數執行上下文:存在無數個,只有在函數被調用的時候才會被創建,每次調用函數都會創建一個新的執行上下文
- Eval 函數執行上下文: 指的是運行在
eval
函數中的代碼,很少用而且不建議使用
下麵給出全局上下文和函數上下文的例子:
紫色框住的部分為全局上下文,藍色和橘色框起來的是不同的函數上下文。只有全局上下文(的變數)能被其他任何上下文訪問
可以有任意多個函數上下文,每次調用函數創建一個新的上下文,會創建一個私有作用域,函數內部聲明的任何變數都不能在當前函數作用域外部直接訪問
二、生命周期
執行上下文的生命周期包括三個階段:創建階段 → 執行階段 → 回收階段
創建階段
創建階段即當函數被調用,但未執行任何其內部代碼之前
創建階段做了三件事:
- 確定 this 的值,也被稱為
This Binding
- LexicalEnvironment(詞法環境) 組件被創建
- VariableEnvironment(變數環境) 組件被創建
偽代碼如下:
ExecutionContext = { ThisBinding = <this value>, // 確定this LexicalEnvironment = { ... }, // 詞法環境 VariableEnvironment = { ... }, // 變數環境 }
This Binding
確定this
的值我們前面講到,this
的值是在執行的時候才能確認,定義的時候不能確認
詞法環境
詞法環境有兩個組成部分:
-
全局環境:是一個沒有外部環境的詞法環境,其外部環境引用為
null
,有一個全局對象,this
的值指向這個全局對象 -
函數環境:用戶在函數中定義的變數被存儲在環境記錄中,包含了
arguments
對象,外部環境的引用可以是全局環境,也可以是包含內部函數的外部函數環境
偽代碼如下:
GlobalExectionContext = { // 全局執行上下文 LexicalEnvironment: { // 詞法環境 EnvironmentRecord: { // 環境記錄 Type: "Object", // 全局環境 // 標識符綁定在這裡 outer: <null> // 對外部環境的引用 } } FunctionExectionContext = { // 函數執行上下文 LexicalEnvironment: { // 詞法環境 EnvironmentRecord: { // 環境記錄 Type: "Declarative", // 函數環境 // 標識符綁定在這裡 // 對外部環境的引用 outer: <Global or outer function environment reference> } }
變數環境
變數環境也是一個詞法環境,因此它具有上面定義的詞法環境的所有屬性
在 ES6 中,詞法環境和變數環境的區別在於前者用於存儲函數聲明和變數( let
和 const
)綁定,而後者僅用於存儲變數( var
)綁定
舉個例子
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
執行上下文如下:
GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { // 詞法環境 EnvironmentRecord: { Type: "Object", // 標識符綁定在這裡 a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer: <null> }, VariableEnvironment: { // 變數環境 EnvironmentRecord: { Type: "Object", // 標識符綁定在這裡 c: undefined, } outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // 標識符綁定在這裡 Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // 標識符綁定在這裡 g: undefined }, outer: <GlobalLexicalEnvironment> } }
留意上面的代碼,let
和const
定義的變數a
和b
在創建階段沒有被賦值,但var
聲明的變數從在創建階段被賦值為undefined
這是因為,創建階段,會在代碼中掃描變數和函數聲明,然後將函數聲明存儲在環境中
但變數會被初始化為undefined
(var
聲明的情況下)和保持uninitialized
(未初始化狀態)(使用let
和const
聲明的情況下)
這就是變數提升的實際原因
執行階段
在這階段,執行變數賦值、代碼執行
如果 Javascript
引擎在源代碼中聲明的實際位置找不到變數的值,那麼將為其分配 undefined
值
回收階段
執行上下文出棧等待虛擬機回收執行上下文
二、執行棧
執行棧,也叫調用棧,具有 LIFO(後進先出)結構,用於存儲在代碼執行期間創建的所有執行上下文
當Javascript
引擎開始執行你第一行腳本代碼的時候,它就會創建一個全局執行上下文然後將它壓到執行棧中
每當引擎碰到一個函數的時候,它就會創建一個函數執行上下文,然後將這個執行上下文壓到執行棧中
引擎會執行位於執行棧棧頂的執行上下文(一般是函數執行上下文),當該函數執行結束後,對應的執行上下文就會被彈出,然後控制流程到達執行棧的下一個執行上下文
舉個例子:
let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); console.log('Inside Global Execution Context');
轉化成圖的形式
簡單分析一下流程:
- 創建全局上下文請壓入執行棧
first
函數被調用,創建函數執行上下文並壓入棧- 執行
first
函數過程遇到second
函數,再創建一個函數執行上下文並壓入棧 second
函數執行完畢,對應的函數執行上下文被推出執行棧,執行下一個執行上下文first
函數first
函數執行完畢,對應的函數執行上下文也被推出棧中,然後執行全局上下文- 所有代碼執行完畢,全局上下文也會被推出棧中,程式結束
參考文獻
- https://zhuanlan.zhihu.com/p/107552264
如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。