詳解JavaScript中的執行上下文及調用堆棧

一、執行上下文是什麼

代碼運行是在一定的環境之中運行的,這個運行環境我們就成為執行環境,也就是執行上下文,按照執行環境不同,我們可以分為三類:

全局執行環境:代碼首次執行時候的默認環境

函數執行環境:每當執行流程進入到一個函數體內部的時候

Eval執行環境:當eval函數內部的文本執行的時候

二、執行上下文棧是什麼

既然是‘棧’,那就得符合‘棧’的特性,即數據結構是先進後出。下面我們看一段代碼:

function cat(a){
    if(a<0){
        return false;
    }
    console.log('入棧:'+a);
    cat(a-1);
    console.log('出棧:'+a);
}
cat(3);
// 入棧:3
// 入棧:2
// 入棧:1
// 入棧:0
// 出棧:0
// 出棧:1
// 出棧:2
// 出棧:3

我們來分析上面的代碼的執行過程:

①瀏覽器加載時,程序進入全局執行上下文,將其壓入一個棧內(第一個進入的,所以最底層);該執行上下文下隻有一個函數cat、cat調用、參數3;

②程序進入cat函數內,即進入該函數執行上下文,也將其壓入棧內(第二個進入的,所以倒數第二層),因為參數為3大於0,繼續往下執行,輸出’入棧:3’;

③程序繼續執行,調用函數cat,再次進入新的函數執行上下文,繼續壓入棧內(第三個進入,倒數第三層),參數為a-1,循環②步驟;這裡,需要註意的是,因為調用瞭函數cat(a-1),導致下一行代碼’出棧:a’(此時a仍為3),也就是’出棧:3’暫時擱淺起來,存在棧內倒數第二層

④不斷重復②③步驟,以次輸出’入棧:2’、’入棧:1’、’入棧:0’;同時被擱淺的有’出棧:2’(棧內倒數第三層)、’出棧:1(棧內倒數第四層)’、’出棧:0(棧內倒數第五層)’;

⑤按照棧的特性,被擱淺起來的4個輸出項:以次輸出’入棧:3’、’入棧:2’、’入棧:1’、’入棧:0’;
以上就是執行上下文棧的具體情況,請大傢手動代碼練習一下,相信會容易理解透徹。

三、執行上下文棧的過程細節

我們已經知道,每次調用函數都會執行一個新的上下文,每個執行上下文都分為兩個階段:創建階段、執行階段
創建階段:指的是程序調用函數,但代碼未執行時的階段;
執行階段:指的是變量分配、函數執行等代碼執行階段;

(一)創建階段

該階段會調用函數數時,創建一個執行上下文對象(executionContextObj),該對象又包含瞭三個對象,分別是作用域鏈對象(scopeChain)、變量對象(variableObject,簡稱VO)、this對象,其中VO包括變量聲明(variable)、函數聲明(function)、參數(arguments)等。
這三個對象分別是在三個步驟創建的:
步驟1:初始化作用域鏈(scopeChain),開辟棧內存

步驟2:創建參數、函數、變量

步驟3:決定上下文的’this’的值

結合代碼來進一步分析一下上面的步驟:

function cat(name) {
    var a = '年年';
    var b = function () {};
    this.name = name;
    function c() {
        console.log(this.name);
    }
    c();
}
cat('有魚');

這段代碼在調用函數 cat(‘有魚’)時,執行上下文是處於 創建階段的,代碼解析為:

cat執行上下文對象 = {
    scopeChain: { ... },// 1.創建作用域鏈,開辟棧內存
    variableObject: { 2.創建變量對象
        arguments: {  // 2.1 解析參數
            0: '有魚',
            length: 1
        },
        name: '有魚',  // 2.1 解析參數,創建形參名稱,並進行參數賦值
        c: function c()  // 2.2 找到函數聲明c,並將c作為屬性,function c作為值
        a: undefined,  // 2.3 找到變量聲明a,初始化為undefined,該階段隻看聲明部分,不進行賦值
        b: undefined   // 2.3 找到變量聲明b,初始化為undefined,該階段隻看聲明部分,不進行賦值
    },
    this: {  3.創建this對象
        this:cat('有魚') // 3.1 指向此次調用的對象
        name:undefined // 3.2 對象屬性name的初始化為undefined
    };
    c() //又進入函數c執行上下文,跟cat函數一樣,暫不展開 
}

通過代碼解析我們可以得出以下結論

①三個步驟順序不能亂
②VO步驟內,先執行函數聲明,再執行變量聲明
③隻有參數可以在此階段進行賦值,變量、函數都隻能聲明

(二)執行階段

該階段js解釋器執行上下文中的函數代碼,逐行運行js代碼,並給變量賦值;
還是結合代碼來分析:

cat執行上下文對象 = {
    scopeChain: { ... },
    variableObject: { 
        arguments: { 
            0: '有魚',
            length: 1
        },
        name: '有魚', 
        c: function c(),
        a: '年年',  // 變量a進行賦值
        b: function b   // 變量b進行賦值
    },
    this: {  3.創建this對象
        this:cat('有魚') 
        name:'有魚' // 對象屬性name進行賦值
    }
}

以上就是詳解JavaScript中的執行上下文及調用堆棧的詳細內容,更多關於JavaScript中的執行上下文及調用堆棧的資料請關註WalkonNet其它相關文章!

推薦閱讀: