執行上下文(Exexution Contexts):用來經過ECMAScript編譯器來追蹤代碼運行時計算的一種規範策略。git
執行上下文簡單理解就是代碼執行時所在環境的抽象。github
執行上下文同時包含變量環境組件(VariableEnvironment)和詞法環境組件(LexicalEnvironment),這兩個組件多數狀況下都指向相同的詞法環境(Lexical Environment),那爲何還要存在兩個環境組件呢?咱們稍後將進行詳細討論。若是不太瞭解詞法環境的能夠看下個人上一篇文章深刻ECMAScript系列(一):詞法環境。函數
ExecutionContext = {
VariableEnvironment: { ... },
LexicalEnvironment: { ... },
}
複製代碼
執行上下文棧(Execution Context Stack):是一個後進先出的棧式結構(LIFO),用來跟蹤維護執行上下文。運行執行上下文(running execution context) 始終位於執行上下文棧的頂層。那麼何時會建立新的執行上下文呢?post
ECMAScript可執行代碼有四種類型:全局代碼,函數代碼,模塊代碼和eval
。每當從當前執行代碼運行至其餘可執行代碼時,會建立新的執行上下文,將其壓入執行上下文棧併成爲正在運行的執行上下文。當相關代碼執行完畢返回後,將正在運行的執行上下文從執行上下文棧刪除,以前的執行上下文又成爲了正在運行的執行上下文。ui
咱們經過一個動圖來看一下執行上下文棧的工做過程spa
上面提到過ECMAScript可執行代碼有四種類型:全局代碼,函數代碼,模塊代碼和eval
。code
這裏雖說是全局代碼,可是JavaScript引擎實際上是按照
script
標籤來解析執行的,也就是說script
標籤按照它們出現的順序解析執行,這也就是爲何咱們平時要將項目依賴js庫放在前面引入的緣由。cdn
JavaScript引擎是按可執行代碼塊來執行代碼的,在任意的JavaScript可執行代碼被執行時,執行步驟可按以下理解:對象
咱們日常所說的變量提高就發生在上述執行步驟的第四步,對代碼塊內的標識符進行實例化及初始化的具體表現以下:ip
let
、const
和class
聲明的標識符合集記錄爲lexNames
var
和function
聲明的標識符合集記錄爲varNames
lexNames
內的任何標識符在varNames
或lexNames
內出現過,則報錯SyntaxError
這就是爲何能夠用
var
或function
聲明多個同名變量,可是不能用let
、const
和class
聲明多個同名變量。
varNames
內的var
聲明的標識符實例化並初始化賦值undefined
,若是有同名標識符則跳過
這就是所謂的變量提高,咱們用
var
聲明的變量,在聲明位置以前訪問並不會報錯,而是返回undefined
lexNames
內的標識符實例化,但並不會進行初始化,在運行至其聲明處代碼時纔會進行初始化,在初始化前訪問都會報錯。
這就是咱們所說的暫時性死區,
let
、const
和class
聲明的變量其實也提高了,只不過沒有被初始化,初始化以前不可訪問。
varNames
內的函數聲明實例化並初始化賦值對應的函數體,若是有同名函數聲明,則前面的都會忽略,只有最後一個聲明的函數會被初始化賦值。
函數聲明會被直接賦值,全部咱們在函數聲明位置以前也能夠調用函數。
首先明確這兩個環境組件的做用,變量環境組件(VariableEnvironment)
用於記錄var
聲明的綁定,詞法環境組件(LexicalEnvironment)
用於記錄其餘聲明的綁定(如let
、const
、class
等)。
通常狀況下一個Exexution Contexts
內的VariableEnvironment
和LexicalEnvironment
指向同一個詞法環境,之因此要區分兩個組件,主要是爲了實現塊級做用域的同時不影響var
聲明及函數聲明。
衆所周知,ES6以前並無塊級做用域的概念,可是ES6及以後咱們能夠經過新增的let
及const
等命令來實現塊級做用域,而且不影響var
聲明的變量和函數聲明,那麼這是怎麼實現的呢?
running Execution Context
)內,詞法環境由VariableEnvironment
和LexicalEnvironment
構成,此執行上下文內的全部標識符的綁定都記錄在兩個組件的環境記錄內。LexicalEnvironment
記錄下來,咱們將其記錄爲oldEnv
。LexicalEnvironment
(外部詞法環境outer
指向oldEnv
),咱們將其記錄爲newEnv
,並將newEnv
設置爲running Execution Context
的LexicalEnvironment
。let
、const
等聲明就會綁定在這個newEnv
上面,可是var
聲明和函數聲明仍是綁定在原來的VariableEnvironment
上面。
塊級代碼內的函數聲明會被當作
var
聲明,會被提高至外部環境,塊級代碼運行前其值爲初始值undefined
console.log(foo) // 輸出:undefined
{
function foo() {console.log('hello')}
}
console.log(foo) // 輸出: ƒ foo() {console.log('hello')}
複製代碼
oldEnv
還原爲running Execution Context
的LexicalEnvironment
。目前包括塊級代碼(在一對大括號內的代碼)、for
循環語句、switch
語句、TryCatch
語句中的catch
從句以及with
語句(with
語句建立的新環境爲對象式環境,其餘皆爲聲明式環境)都是這樣來實現塊級做用域的。
準備將以前寫的部分深刻ECMAScript文章重寫,加深本身理解,使內容更有乾貨,目錄結構也更合理。
歡迎前往閱讀系列文章,若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。
菜鳥一枚,若是有疑問或者發現錯誤,能夠在相應的 issues 進行提問或勘誤,與你們共同進步。