JavaScript基礎專題之執行上下文和執行棧(二)

執行順序

通常執行順序很顯然按照建立順序執行,對大對數開發者來講並不陌生。javascript

像是這樣:java

foo();  // 報錯
var foo = function () {

    console.log('foo1');

}

foo();  // foo1

var foo = function () {

    console.log('foo2');

}

foo(); // foo2
複製代碼

然而有的時候會是這樣:數組

foo();  // foo2

function foo() {

    console.log('foo1');

}

foo();  // foo2

function foo() {

    console.log('foo2');

}

foo(); // foo2
複製代碼

咱們能夠看到,做爲函數調用的時候,會出現三個foo2。其實 JavaScript 引擎並不是一行一行地分析和執行程序,在執行以前會對一些對結構進行分析執行。好比第一個例子中的變量提高,和第二個例子中的函數提高。 那麼JS又是如何進行結構化的解析的呢?執行過程當中又作了什麼動做呢?bash

棧又是什麼?異步

圖片來自MDN

這張圖分別展現了棧、堆和隊列,其中棧就是咱們所說的執行上下文棧;堆是用於存儲複雜類型好比:對象,數組等等。隊列就是異步隊列,用於事件循環(event loop)的執行。 JS代碼在引擎中是以代碼片斷的方式來分析執行的,而並不是一行一行來分析執行。函數

簡單的例子oop

//入棧過程
Stack.push("chris")

Stack.push("james")

Stack.push("kobe")

//出棧過程

Stack.pop() //["chirs","james"]

Strack.pop() //["chirs"]

Stack.pop() //[]
複製代碼

能夠看出,棧是執行過程是一個先入後出的過程。post

可執行代碼

而這些代碼片斷的可執行代碼無非爲三種:全局代碼(Global code)函數代碼(Function Code)eval代碼(Eval code)。 這些可執行代碼在執行的時候又會建立一個一個的執行上下文(Execution context)。例如,當執行到一個 函數的時候,JS引擎會作一些「準備工做」,而這個「準備工做」,咱們稱其爲執行上下文。 那麼隨着咱們的執行上下文數量的增長,JS引擎又如何去管理這些執行上下文呢?這時便有了執行上下文棧ui

執行上下文棧

問題來了,咱們寫的函數多了去了,如何管理建立的那麼多執行上下文呢?spa

因此 JavaScript 引擎建立了執行上下文棧(Execution context stack,ECS)來管理執行上下文

爲了模擬執行上下文棧的行爲,讓咱們定義執行上下文棧是一個數組:

Stack= [];
複製代碼

試想當 JavaScript 開始要解釋執行代碼的時候,最早遇到的就是全局代碼,因此初始化的時候首先就會向執

行上下文棧壓入一個全局執行上下文,咱們用 globalContext 表示它,而且只有當整個應用程序結束的時候,

Stack纔會被清空,因此程序結束以前, Stack最底部永遠有個 globalContext:

Stack = [
    globalContext// 一開始只有全局上下文
];
複製代碼

一個簡單的例子:

function fun3() {
    console.log('fun3')
}

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();
複製代碼

每當一個函數執行,就會建立一個執行上下文,而且壓入執行上下文棧,當函數執行完畢的時候,就會將函

數的執行上下文從棧中彈出。

// fun1()
Stack .push(<fun1> functionContext); 
//[globalContext,<fun1> functionContext]

// fun1中有fun2,還要建立fun2的執行上下文
Stack .push(<fun2> functionCofuntext);
//[globalContext,<fun1> functionContext,<fun2> functionCofuntext],

// fun2還調用了fun3
Stack .push(<fun3> functionContext);
//[globalContext,<fun1> functionContext,<fun2> functionCofuntext,<fun3> functionContext]      

// fun3執行完畢 
Stack .pop();
//[globalContext,<fun1> functionContext,<fun2> functionCofuntext]

// fun2執行完畢
Stack .pop();
//[globalContext,<fun1> functionContext]

// fun1執行完畢
Stack .pop();
//[globalContext]

// javascript接着執行下面的代碼,可是Stack 底層永遠有個globalContext
複製代碼

一個複雜的例子:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();
複製代碼
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
複製代碼

兩段代碼執行的結果同樣,可是兩段代碼究竟有哪些不一樣呢?

答案就是執行上下文棧的變化不同。

先模擬第一段代碼:

Stack.push(<checkscope> functionContext);
Stack.push(<f> functionContext);
Stack.pop();
Stack.pop();
複製代碼

再模擬第二段代碼:

Stack.push(<checkscope> functionContext);
Stack.pop();
Stack.push(<f> functionContext);
Stack.pop();
複製代碼

咱們能夠發現二三部分出棧和入棧是不一樣的。

總結

  1. JS代碼在引擎中是以一段一段的方式來分析執行的,而並不是一行一行來分析執行
  2. 可執行代碼分爲三種:全局代碼(Global code)函數代碼(Function Code)eval代碼(Eval code),其中全局代碼函數代碼比較常見,關於eval代碼可參考JavaScript 爲何不推薦使用 eval?
  3. 每遇到函數執行的時候,就會建立一個執行上下文執行上下文會進入執行上下文棧
  4. 程序開始最早入棧和程序結束最後出棧的都是全局執行上下文

JavaScript基礎專題系列

JavaScript基礎專題系列目錄地址:

JavaScript基礎專題之原型與原型鏈(一)

新手寫做,若是有錯誤或者不嚴謹的地方,請大夥給予指正。若是這篇文章對你有所幫助或者有所啓發,還請給一個贊,鼓勵一下做者,在此謝過。

相關文章
相關標籤/搜索