深刻javascript之執行上下文

聊聊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

三,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. 函數申明先於變量申明

參考:
深刻js之執行上下文
Execution Context

相關文章
相關標籤/搜索