摘要:棧底為全局上下文,棧頂為當前正在執(zhí)行的上下文。位于棧頂?shù)纳舷挛膱?zhí)行完畢后會自動出棧,依次向下直至所有上下文運行完畢,最后瀏覽器關閉時全局上下文被銷毀。
講清楚之執(zhí)行上下文
標簽 : javascript
什么是執(zhí)行上下文?當 JavaScript 代碼執(zhí)行一段可執(zhí)行代碼時,會創(chuàng)建對應的上下文(execution context)并將該上下文壓入上下文棧(context stack)中。
上下文包含以下3個重要屬性:
name | - | |
---|---|---|
變量對象(VO, variable object) | 當前函數(shù)定義的變量、函數(shù)、參數(shù) | |
作用域鏈(Scope chain) | 源代碼定義時形成的作用域鏈 | |
this |
上下文是一個抽象概念,為了便于理解我們假設上下文是一個對象并且包含VO、Scope、this這三個屬性:
function foo (c) { let a = 1 let b = function () {} } // foo函數(shù)的上下文 fooContext = { VO: { arguments: { // 實參 c: undefind, length: 0 }, a: 1, // 變量 b: reference to function (){} // 函數(shù) }, Scope: [VO, globalContext.VO], // 作用域鏈 this: undefind // 非嚴格模式下為 this }
所以上下文是函數(shù)運行時的環(huán)境或者說是依賴資源的集合,它決定了函數(shù)運行時可以獲取到哪些變量、函數(shù)。
執(zhí)行上下文(EC): 如果函數(shù)處于正在執(zhí)行狀態(tài)則該函數(shù)的上下文稱為執(zhí)行上下文, 與此同時如果函數(shù)處于非執(zhí)行狀態(tài)則為(普通)上下文。所以執(zhí)行上下文只是上下文的不同狀態(tài),本質上它們沒有區(qū)別。
上下文棧上下文棧又稱為執(zhí)行棧(ECS), 瀏覽器中 javascript 解析器本身是單線程的,即同一時間只能處理一個上下文及對應的代碼段,所以 javascript 解析引擎使用上下文棧來管理上下文。所有的上下文創(chuàng)建后會保存在上下文棧隊列里。棧底為全局上下文,棧頂為當前正在執(zhí)行的上下文。
一個上下文就是一個執(zhí)行單元, javascript 以棧的方式管理執(zhí)行單元。頁面初始化的時候首先會在棧底壓入全局上下文,然后根據(jù)規(guī)則執(zhí)行到可執(zhí)行函數(shù)時會將函數(shù)的上下文壓入上下文棧 中, 被壓入的上下文包含有該函數(shù)運行時所需的資源(變量對象、作用域鏈、this),這些資源提供給函數(shù)運行時的表達式使用。
執(zhí)行上下文可以理解為函數(shù)運行時的環(huán)境。同時執(zhí)行上下文也是一個不可見的概念。
javascript 中有3種運行環(huán)境:
全局環(huán)境: 在瀏覽器中是window, 在 node 環(huán)境中是global,當頁面初始化時會將全局上下文壓入上下文棧;
函數(shù)環(huán)境: 當函數(shù)被調用執(zhí)行時會收集該函數(shù)的資源,創(chuàng)建上下文并壓入上下文棧;
eval環(huán)境,棄用
一個運行環(huán)境會對應一個上下文。位于棧頂?shù)纳舷挛膱?zhí)行完畢后會自動出棧,依次向下直至所有上下文運行完畢,最后瀏覽器關閉時全局上下文被銷毀。為了好理解來舉個栗子:
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"
上面代碼的邏輯就是先執(zhí)行don(),然后是too()、foo()。執(zhí)行到foo()時的上下文棧是這樣的:
我們假設上下文棧為一個數(shù)組:ECStack :
ECStack = []
javascript 載入完成后首先解析執(zhí)行的是全局代碼,所以初始化的時候會向上下文棧中 push 全局上下文,我們用globalContext來表示。
ECStack = [ globalContext ]
全局作用域在整個代碼運行階段會一直存在,直至頁面關閉時 ECStack 會被請空,從而globalContext則被銷毀。
全局上下文創(chuàng)建的時候進行變量提升、生成變量對象等操作,而后會執(zhí)行當前上下文中的可執(zhí)行代碼(函數(shù)、表達式)。遇到函數(shù)調用的時候會向上下文棧中push該函數(shù)的上下文。
function foo () { console.log("foo") } function too () { console.log("too") foo() } function don () { too() } don()
執(zhí)行邏輯可以理解為:
執(zhí)行到 don(), 解析 don函數(shù)內部代碼
生成 don 函數(shù)的上下文(vo、Scope chain、this)
壓入 don 的上下文到 ECStack
執(zhí)行 don 函數(shù)體內部的表達式
執(zhí)行 too()
生成 too 函數(shù)的上下文(vo、Scope chain、this)
壓入 too 的上下文到 ECStack
...
javascript 解析器不斷遞歸直到 foo 函數(shù)執(zhí)行完...foo 函數(shù)上下文被彈出...然后回溯到globalContext上下文...等待...當事件的回調函數(shù)被激活后,執(zhí)行回調函數(shù)。( 這里涉及到 javascript 的執(zhí)行機制和事件循環(huán),請關注后續(xù)文章^_^)
執(zhí)行邏輯的偽代碼如下:
// 偽代碼 // don() ECStack.push(functionContext); // 在don中調用了too, push too的上下文到上下文棧里 ECStack.push( functionContext); // 在too中調用了foo, push foo的上下文到上下文棧里 ECStack.push( functionContext); // foo執(zhí)行完畢, 彈出上下文 ECStack.pop(); // too執(zhí)行完畢, 彈出上下文 ECStack.pop(); // don執(zhí)行完畢, 彈出上下文 ECStack.pop(); // 非全局上下文執(zhí)行完畢被彈出后會一直停留在全局上下文里,直至頁面關閉
需要注意的是,上下文與作用域(scope)是不同的概念。上下文是一個運行時概念,瀏覽器運行后執(zhí)行 js 代碼,將不同的上下文加入上下文棧中,頂層的上下文對應的代碼塊執(zhí)行完后又將該上下文銷毀。 而作用域是一個靜態(tài)概念,根據(jù)所在代碼片段的位置及詞法關系確立的,不管瀏覽器運行與否,源代碼的作用域關系、變量的訪問權限依然不變。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/95233.html
摘要:講清楚之參數(shù)傳值參數(shù)傳值是指函數(shù)調用時,給函數(shù)傳遞配置或運行參數(shù)的行為,包括通過進行傳值。所以對的賦值會改變上下文棧中標識符保存的具體值此時如果使用的是按引用傳遞,則變量所指向的對象因該也被賦值為。 講清楚之 javascript 參數(shù)傳值 參數(shù)傳值是指函數(shù)調用時,給函數(shù)傳遞配置或運行參數(shù)的行為,包括通過call、apply 進行傳值。 在實際開發(fā)中,我們總結javascript參數(shù)傳...
摘要:講清楚之中的這一節(jié)來探討。所以當函數(shù)作為構造函數(shù)調用,則函數(shù)內部的綁定到該函數(shù)上。在通過構造函數(shù)實例化對象時,對象內部的也同樣指向該實例對象。 講清楚之 javascript中的this 這一節(jié)來探討this。 在 javascript 中 this 也是一個神的存在,相對于 java 等語言在編譯階段確定,而在 javascript 中, this 是動態(tài)綁定,也就是在運行期綁定的。...
閱讀 1010·2023-04-25 19:35
閱讀 2672·2021-11-22 09:34
閱讀 3702·2021-10-09 09:44
閱讀 1729·2021-09-22 15:25
閱讀 2944·2019-08-29 14:00
閱讀 3378·2019-08-29 11:01
閱讀 2606·2019-08-26 13:26
閱讀 1741·2019-08-23 18:08