聊聊js的執行上下文javascript
EC : 執行上下文
ECS : 執行環境棧
VO : 變量對象
AO : 活動對象
scope chain :做用域鏈
javascript運行的代碼環境有三種:java
全局代碼:代碼默認運行的環境,最早會進入到全局環境中 函數代碼:在函數的局部環境中運行的代碼 Eval代碼:在Eval()函數中運行的代碼
全局上下文是最外圍的一個執行環境,web瀏覽器中被認爲是window對象。在初始化代碼時會先進入全局上下文中,每當一個函數被調用時就會爲該函數建立一個執行上下文,每一個函數都有本身的執行上下文。來看一段代碼:web
function f1() { var f1Context = 'f1 context'; function f2() { var f2Context = 'f2 context'; function f3() { var f3Context = 'f3 context'; console.log(f3Context); } f3(); console.log(f2Context); } f2(); console.log(f1Context); } f1();
這段代碼有4個執行上下文:全局上下文和f1(),f2(),f3()屬於本身的執行上下文。瀏覽器
全局上下文擁有變量f1(),f1()的上下文中有變量f1Context和f2(),f2()的上下文有變量f2Context和f3(),f3()上下文有變量f3Context。函數
在這咱們瞭解下執行環境棧ECS,一段代碼全部的執行上下文都會被推入棧中等待被執行,由於js是單線程,任務都爲同步任務的狀況下某一時間只能執行一個任務,執行一段代碼首先會進入全局上下文中,並將其壓入ECS中,執行f1()會爲其建立執行上下文壓入棧頂,f1()中有f2(),再爲f2()建立f2()的執行上下文,依次,最終全局上下文被壓入到棧底,f3()的執行上下文在棧頂,函數執行完後,ECS就會彈出其上下文,f3()上下文彈出後,f2()上下文來到棧頂,開始執行f2(),依次,最後ECS中只剩下全局上下文,它等到應用程序退出,例如瀏覽器關閉時銷燬。this
總結:(執行上下文就用EC替代)spa
1. 全局上下文壓入棧頂 2. 執行某一函數就爲其建立一個EC,並壓入棧頂 3. 棧頂的函數執行完以後它的EC就會從ECS中彈出,而且變量對象(VO)隨之銷燬 4. 全部函數執行完以後ECS中只剩下全局上下文,在應用關閉時銷燬
你們再看一道道題:.net
function foo(i) { if(i == 3) { return; } foo(i+1); console.log(i); } foo(0);
你們明白執行上下文的進棧出棧就應該知道結果爲何是2,1,0線程
ECS棧頂爲foo(3)的的上下文,直接return彈出後,棧頂變成foo(2)的上下文,執行foo(2),輸出2並彈出,執行foo(1),輸出1並彈出,執行foo(0),輸出0並彈出,關閉瀏覽器後全局EC彈出,因此結果爲2,1,0指針
剛纔提到VO,咱們來了解什麼是VO
建立執行上下文時與之關聯的會有一個變量對象,該上下文中的全部變量和函數全都保存在這個對象中。
進入到一個執行上下文時,此執行上下文中的變量和函數均可以被訪問到,能夠理解爲被激活
談到了上下文的建立和執行,咱們來看看EC創建的過程:
創建階段:(函數被調用,可是還未執行函數中的代碼) 1. 建立變量,參數,函數,arguments對象 2. 創建做用域鏈 3. 肯定this的值 執行階段:變量賦值,函數引用,執行代碼
執行上下文爲一個對象,包含VO,做用域鏈和this
executionContextObj = { variableObject: { /* 函數中的arguments對象, 參數, 內部的變量以及函數聲明 */ }, scopeChain: { /* variableObject 以及全部父執行上下文中的variableObject */ }, this: {} }
具體過程:
1. 找到當前上下文調用函數的代碼 2. 執行代碼以前,先建立執行上下文 3. 建立階段: 3-1. 建立變量對象(VO): 1. 建立arguments對象,檢查當前上下文的參數,創建該對象下的屬性和屬性值 2. 掃描上下文的函數申明: 1. 每掃描到一個函數什麼就會在VO裏面用函數名建立一個屬性, 爲一個指針,指向該函數在內存中的地址 2. 若是函數名在VO中已經存在,對應的屬性值會被新的引用覆蓋 3. 掃描上下文的變量申明: 1. 每掃描到一個變量就會用變量名做爲屬性名,其值初始化爲undefined 2. 若是該變量名在VO中已經存在,則直接跳過繼續掃描 3-2. 初始化做用域鏈 3-3. 肯定上下文中this的指向 4. 代碼執行階段 4-1. 執行函數體中的代碼,給VO中的變量賦值
看代碼理解:
function foo(i) { var a = 'hello'; var b = function privateB() {}; function c() {} } foo(22);
調用foo(22)時建立上下文包括VO,做用域鏈,this值
以函數名做爲屬性值,指向該函數在內存中的地址;變量名做爲屬性名,其初始化值爲undefined
注意:函數申明先於變量申明
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c(), a: undefined, b: undefined }, scopeChain: { ... }, this: { ... } }
建立階段結束後就會進入代碼執行階段,給VO中的變量賦值
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c(), a: 'hello', b: pointer to function privateB() }, scopeChain: { ... }, this: { ... } }
function foo() { console.log(f1); //f1() {} console.log(f2); //undefined var f1 = 'hosting'; var f2 = function() {} function f1() {} } foo();
調用foo()時會建立VO,初始VO中變量值等有一系列的過程,全部變量初始化值爲undefined,因此console.log(f2)的值爲undefined。而且函數申明先於變量申明,因此console.log(f1)的值爲f1()函數而不爲hosting
1. 調用函數時會爲其建立執行上下文,並壓入執行環境棧的棧頂,執行完畢 彈出,執行上下文被銷燬,隨之VO也被銷燬 2. EC建立階段分建立階段和代碼執行階段 3. 建立階段初始變量值爲undefined,執行階段才爲變量賦值 4. 函數申明先於變量申明