標籤 : javascriptjavascript
當 JavaScript 代碼執行一段可執行代碼時,會建立對應的上下文(execution context)並將該上下文壓入上下文棧(context stack)中。java
上下文包含如下3個重要屬性:node
name | - | |
---|---|---|
變量對象(VO, variable object) | 當前函數定義的變量、函數、參數 | |
做用域鏈(Scope chain) | 源代碼定義時造成的做用域鏈 | |
this |
上下文是一個抽象概念,爲了便於理解咱們假設上下文是一個對象而且包含VO、Scope、this這三個屬性:數組
function foo (c) { let a = 1 let b = function () {} } // foo函數的上下文 fooContext = { VO: { arguments: { // 實參 c: undefind, length: 0 }, a: 1, // 變量 b: reference to function (){} // 函數 }, Scope: [VO, globalContext.VO], // 做用域鏈 this: undefind // 非嚴格模式下爲 this }
因此上下文是函數運行時的環境或者說是依賴資源的集合,它決定了函數運行時能夠獲取到哪些變量、函數。瀏覽器
執行上下文(EC): 若是函數處於正在執行狀態則該函數的上下文稱爲執行上下文, 與此同時若是函數處於非執行狀態則爲(普通)上下文。因此執行上下文
只是上下文
的不一樣狀態,本質上它們沒有區別。函數
上下文棧又稱爲執行棧(ECS), 瀏覽器中 javascript 解析器自己是單線程的,即同一時間只能處理一個上下文及對應的代碼段,因此 javascript 解析引擎使用上下文棧來管理上下文。全部的上下文建立後會保存在上下文棧隊列裏。棧底爲全局上下文,棧頂爲當前正在執行的上下文。this
一個上下文就是一個執行單元, javascript 以棧的方式管理執行單元。頁面初始化的時候首先會在棧底壓入全局上下文,而後根據規則執行到可執行函數時會將函數的上下文壓入上下文棧
中, 被壓入的上下文包含有該函數運行時所需的資源(變量對象、做用域鏈、this),這些資源提供給函數運行時的表達式使用。spa
執行上下文能夠理解爲函數運行時的環境。同時執行上下文也是一個不可見的概念。線程
javascript 中有3種運行環境:code
window
, 在 node 環境中是global
,當頁面初始化時會將全局上下文壓入上下文棧;一個運行環境會對應一個上下文。位於棧頂的上下文執行完畢後會自動出棧,依次向下直至全部上下文運行完畢,最後瀏覽器關閉時全局上下文被銷燬。爲了好理解來舉個栗子:
let i = 0 function foo () { i++ console.log(i, 'foo') } function too () { i++ console.log(i, 'too') foo() } function don () { i++ console.log(i, 'don') too() } don() // 1 "don" // 2 "too" // 3 "foo"
上面代碼的邏輯就是先執行don(),而後是too()、foo()。執行到foo()時的上下文棧是這樣的:
咱們假設上下文棧爲一個數組:ECStack
:
ECStack = []
javascript 載入完成後首先解析執行的是全局代碼,因此初始化的時候會向上下文棧中 push 全局上下文,咱們用globalContext
來表示。
ECStack = [ globalContext ]
全局做用域在整個代碼運行階段會一直存在,直至頁面關閉時 ECStack
會被請空,從而globalContext
則被銷燬。
全局上下文建立的時候進行變量提高、生成變量對象等操做,然後會執行當前上下文中的可執行代碼(函數、表達式)。遇到函數調用的時候會向上下文棧中push
該函數的上下文。
function foo () { console.log('foo') } function too () { console.log('too') foo() } function don () { too() } don()
執行邏輯能夠理解爲:
javascript 解析器不斷遞歸直到 foo 函數執行完...foo 函數上下文被彈出...而後回溯到globalContext
上下文...等待...當事件的回調函數被激活後,執行回調函數。( 這裏涉及到 javascript 的執行機制和事件循環,請關注後續文章^_^)
執行邏輯的僞代碼以下:
// 僞代碼 // don() ECStack.push(<don> functionContext); // 在don中調用了too, push too的上下文到上下文棧裏 ECStack.push(<fun2> functionContext); // 在too中調用了foo, push foo的上下文到上下文棧裏 ECStack.push(<fun3> functionContext); // foo執行完畢, 彈出上下文 ECStack.pop(); // too執行完畢, 彈出上下文 ECStack.pop(); // don執行完畢, 彈出上下文 ECStack.pop(); // 非全局上下文執行完畢被彈出後會一直停留在全局上下文裏,直至頁面關閉
須要注意的是,上下文與做用域(scope)是不一樣的概念。上下文是一個運行時概念,瀏覽器運行後執行 js 代碼,將不一樣的上下文加入上下文棧中,頂層的上下文對應的代碼塊執行完後又將該上下文銷燬。 而做用域是一個靜態概念,根據所在代碼片斷的位置及詞法關係確立的,無論瀏覽器運行與否,源代碼的做用域關係、變量的訪問權限依然不變。