https://juejin.im/post/5ba321...
https://juejin.im/entry/59986...
我只是搬運工,看了他們的文章後深有啓發,因而把他們的精華彙總而後加入本身的理解整理了這一篇文章。數組
這是一個很是抽象的概念,你無需完全的弄明白它的意思,你只須要明白它作了什麼。瀏覽器
在充分理解他作了什麼以前仍是要了解一下它究竟是什麼數據結構
Execution Context(執行上下文)是 ECMA-262 標準中定義的一個 抽象概念,用於同 Executable
Code(可執行代碼)進行區分。
1:什麼是執行代碼----Executable Codeide
合法的,能夠被解釋器解析執行的代碼。
分爲三類函數
2:什麼是執行上下文----Execution Contextpost
執行上下文 是 ES 用來 跟蹤代碼運行狀態和相關資源集合的特殊機制。它 決定了執行代碼執行的過程當中能夠訪問的數據。每當 Javascript 代碼在運行的時候,它都是在執行上下文中運行。this
分爲三類code
這是默認或者說基礎的上下文,任何不在函數內部的代碼都在全局上下文中執行。它會執行兩件事:建立一個全局的 window
對象(瀏覽器的狀況下),而且設置 this 的值等於這個全局對象。一個程序中只會有一個全局執行上下文。
每當一個函數被調用時, 都會爲該函數建立一個新的上下文。每一個函數被調用時都有它本身的執行上下文。函數上下文能夠有任意多個。每當一個新的執行上下文被建立,它會按定義的順序(將在後文討論)執行一系列步驟。
因爲 JavaScript 開發者並不常用 eval,因此在這裏我不會討論它。
3:執行上下文的基本工做方式對象
先理解兩個名詞:執行上下文棧(Execution Context Stack)、運行執行上下文(Running Execution Context)遞歸
執行上下文棧( Execution Context Stack ):用來保存全部執行上下文的棧,是一種擁有 LIFO( 後進先出)數據結構的棧。 當 JavaScript 引擎第一次遇到你的腳本時,它會建立一個全局的執行上下文而且壓入當前執行棧。每當引擎遇到一個函數調用,它會爲該函數建立一個新的執行上下文並壓入棧的頂部。引擎會執行那些執行上下文位於棧頂的函數。當該函數執行結束時,執行上下文從棧中彈出,控制流程到達當前棧中的下一個上下文。
運行執行上下文( Running Execution Context ):正在使用的執行上下文。在任意時間,最多隻能有一
個正在運行代碼的執行上下文。
4:基本工做方式
運行執行上下文老是在執行上下文棧的頂部,全局執行上下文總在執行上下文棧的底部。不管何時,只要控制權從與當前運行執行上下文相關的可執行代碼上切換到另外一部分與當前運行執行上下文不相關的可執行代碼上,一個新的執行上下文就會被建立,新建立的執行上下文會被放在當前的運行執行上下文的上面,成爲新的運行執行上下文。
5:具體工做流程
如前言中提到的,ES 標準中並無從技術實現的角度定義執行上下文準確類型和結構,爲了更方便地解釋
執行代碼和執行上下文之間的關係,暫且用數組表示執行上下文棧,而後用僞代碼來操做執行上下文棧:
DCStack = [] // 執行上下文棧
<1:開始執行代碼:全局執行代碼與全局執行上下文
解析器在解析執行代碼時首先執行全局代碼,爲其建立對應的執行上下文,全局上下文被壓入執行上下文棧
ECStack = [ globalContext // 全局執行上下文 ]
<2:開始執行函數:函數代碼與函數執行上下文
注意:函數代碼中不包括內部函數的代碼
運行下面的函數 (function foo(bar) { if (bar) { return } foo(true); })() 咱們用僞代碼還原一下執行棧中發生了什麼?? // 第一次調用 foo ECStack = [ <foo> functionContext, globalContext ] // 第二次調用 foo ECStack = [ <foo> functionContext – recursively(遞歸), <foo> functionContext, globalContext ]
咱們看一個實際的例子
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');
首先執行這段代碼,解析器解析到了這段代碼,因而先建立了一個全局上下文,並把全局上下文壓入執行棧
ECStack = [ Global Context ]
而後解析器檢測到了 first(),開始調用first函數,因而建立了一個first函數上下文,並把這個函數向下文壓入到執行棧的頂部(通常執行棧的頂部都是正在運行的上下文,如今正在調用first函數,因此頂部就是他的上下文)
ECSstack= [ First Function Context-----(頂部是正在執行的上下文) Global Context ]
在first() 函數內部又調用了second()函數,因而JavaScript 引擎爲second()函數建立了一個屬於他的執行上下文,並把它壓入執行棧的最頂部。(由於如今執行second()函數,因此他的執行上下文就在最頂部,由於first()函數沒有執行完因此他的執行上下文依然在執行棧的隊列中)
ECSstack = [ Cecond Function Context-----(頂部是正在執行的上下文) First Function Context Global Context ]
執行完second()函數以後,它的執行上下文會自動從執行棧彈出,而且控制流程執行下一個執行上下文,即 first() 函數的執行上下文。
ECSstack= [ First Function Context-----(頂部是正在執行的上下文) Global Context ]
當 first() 執行完畢,它的執行上下文自動從棧彈出,控制流程按順序到達全局執行上下文。一旦全部代碼執行完畢,JavaScript 引擎從當前棧中移除全局執行上下文。
ECStack = [ Global Context ]
6:JavaScript 引擎是怎麼建立執行上下文?
建立執行上下文有兩個階段:1>:建立階段 和 2>:執行階段。
1>:建立階段--(The Creation Phase)
在建立階段會發生三件事
ExecutionContext = { ThisBinding = <this value>, // this綁定 LexicalEnvironment = { ... }, // 詞法環境 VariableEnvironment = { ... }, // 變量環境 }
在全局執行上下文中,this 的值指向全局對象。(在瀏覽器中,this引用 Window 對象)。在函數執行上下文中,this 的值取決於該函數是如何被調用的。若是它被一個引用對象調用,那麼 this 會被設置成那個對象,不然 this 的值被設置爲全局對象或者 undefined(在嚴格模式下)。例如:
let foo = { baz: function() { console.log(this); } } foo.baz(); // 'this' 引用 'foo', 由於 'baz' 被對象 'foo' 調用 let bar = foo.baz; bar(); // 'this' 指向全局 window 對象,由於沒有指定引用對象
詞法環境是一種 規範類型,基於 ECMAScript 代碼的詞法嵌套結構 來定義標識符和具體變量和函數的關聯。一個詞法環境由 環境記錄器和一個可能的 引用外部詞法環境的空值組成。有點沒明白
簡單來講詞法環境是一種定義標識符以及變量的嵌套結構。(這裏的標識符指的是變量/函數的名字,而變量是對實際對象[包含函數類型對象]或原始數據的引用)。
在詞法環境的內部有兩個部件組成:
1:環境記錄器:是存儲變量和函數聲明的實際 位置。:2: 外部環境的引用:意味着它能夠訪問其父級詞法環境(做用域)。
詞法環境有兩種類型:
1:全局環境:(在全局執行上下文中)是沒有外部環境引用的詞法環境。全局環境的外部環境引用是 null。它擁有內建的
Object/Array/等、在環境記錄器內的原型函數(關聯全局對象,好比 window 對象)還有任何用戶定義的全局變量,而且
this的值指向全局對象。2:函數環境:函數內部用戶定義的變量存儲在環境記錄器中。而且引用的外部環境多是全局環境,或者任何包含此內部函數的外部函數。
環境記錄器也有兩種類型:
1:聲明式環境記錄器存儲變量、函數和參數。2:對象環境記錄器用來定義出如今全局上下文中的變量和函數的關係。
簡而言之,
環境記錄器在 全局環境中,環境記錄器是 對象環境記錄器。 在 函數環境中,環境記錄器是 聲明式環境記錄器。
注意
函數環境,聲明式環境記錄器還包含了一個傳遞給函數的 arguments 對象(此對象存儲索引和參數的映射和傳遞給函數的參數的length)
抽象地講,詞法環境在僞代碼中看起來像這樣:
GlobalExectionContext = { // 全局執行上下文 LexicalEnvironment: { // 詞法環境組件 EnvironmentRecord: { // 環境記錄器 ---對象環境記錄器 Type: "Object", // 在這裏綁定標識符 } outer: <null> // 外部環境引用, 是沒有外部環境引用的詞法環境。全局環境的外部環境引用是 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 = { 1:ThisBinding: <Global Object>, //this綁定 2: LexicalEnvironment: { // 詞法環境 --全局的詞法環境 EnvironmentRecord: { //環境記錄器 Type: "Object", // 在這裏綁定標識符 a: < uninitialized >, // 變量a的綁定(let) b: < uninitialized >, // 變量b 的綁定(const) multiply: < func > // 函數聲明 } outer: <null> // 外部環境的引用nul }, 3: VariableEnvironment: { // 變量環境 --全局的詞法環境 EnvironmentRecord: { //環境記錄器 Type: "Object", // 在這裏綁定標識符 c: undefined, // 變量c 的綁定(var) } outer: <null> // 外部環境的引用nul } } // 函數的執行上下文-----(只有遇到調用函數 multiply 時,函數執行上下文才會被建立) FunctionExectionContext = { 1:ThisBinding: <Global Object>, // this 綁定 2:LexicalEnvironment: { //詞法環境 --函數的詞法環境 EnvironmentRecord: { // 環境記錄器 Type: "Declarative", // 在這裏綁定標識符 Arguments: {0: 20, 1: 30, length: 2}, // 聲明式環境記錄器還包含了一個傳遞給函數的 arguments 對象(此對象存儲函數參數鍵值對和傳遞給函數的參數的length)。 }, outer: <GlobalLexicalEnvironment> // 外部環境的引用是全局環境 }, 3:VariableEnvironment: { //變量環境 EnvironmentRecord: { // 環境記錄器 Type: "Declarative", // 在這裏綁定標識符 g: undefined // 變量g的綁定(var) }, outer: <GlobalLexicalEnvironment> // 外部環境的引用是全局環境 } }
可能你已經注意到 let 和 const 定義的變量並無關聯任何值,但 var 定義的變量被設成了 undefined。
這是由於在建立階段時,引擎檢查代碼找出變量和函數聲明,雖然函數聲明徹底存儲在環境中,可是變量最初設置爲 undefined(var
狀況下),或者未初始化(let 和 const 狀況下)。 這就是爲何你能夠在聲明以前訪問 var 定義的變量(雖然是
undefined),可是在聲明以前訪問 let 和 const 的變量會獲得一個引用錯誤。 這就是咱們說的 變量聲明提高。