聊一聊javascript執行上下文

赫子子發表於2018-04-03

跟大家聊聊js的執行上下文

一,相關概念

EC : 執行上下文

ECS : 執行環境棧

VO : 變數物件

AO : 活動物件

scope chain :作用域鏈

二,執行上下文

javascript執行的程式碼環境有三種:

    全域性程式碼:程式碼預設執行的環境,最先會進入到全域性環境中
    函式程式碼:在函式的區域性環境中執行的程式碼
    Eval程式碼:在Eval()函式中執行的程式碼
複製程式碼

全域性上下文是最外圍的一個執行環境,web瀏覽器中被認為是window物件。在初始化程式碼時會先進入全域性上下文中,每當一個函式被呼叫時就會為該函式建立一個執行上下文,每個函式都有自己的執行上下文。來看一段程式碼:

    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中只剩下全域性上下文,它等到應用程式退出,例如瀏覽器關閉時銷燬

總結:(執行上下文就用EC替代)

    1. 全域性上下文壓入棧頂

    2. 執行某一函式就為其建立一個EC,並壓入棧頂

    3. 棧頂的函式執行完之後它的EC就會從ECS中彈出,並且變數物件(VO)隨之銷燬

    4. 所有函式執行完之後ECS中只剩下全域性上下文,在應用關閉時銷燬
複製程式碼

聊一聊javascript執行上下文

大家再看一道道題:

    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

三,VO/AO

VO(變數物件)

建立執行上下文時與之關聯的會有一個變數物件,該上下文中的所有變數和函式全都儲存在這個物件中。

AO(活動物件)

進入到一個執行上下文時,此執行上下文中的變數和函式都可以被訪問到,可以理解為被啟用

談到了上下文的建立和執行,我們來看看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. 函式申明先於變數申明
複製程式碼

參考:Execution Context

相關文章