「JS篇」JavaScript 執行上下文和提升

我們通常將 JavaScript 歸類爲動態或解釋執行語言,但實際上它也是一門編譯語言,它有自己的編譯器形式,運行在 JavaScript 引擎中。

每個 Web 瀏覽器都有自己的 JavaScript 引擎形式:Chrome 有 V8,Mozilla 有 SpiderMonkey 等。這些 JavaScript 引擎的共同點都是將 JavaScript 代碼轉換爲編譯器可以理解的語言,然後執行它。

執行上下文 Execution Context

當 JavaScript 代碼運行的時候,運行 JavaScript 代碼的環境形成了執行上下文 ,執行上下文決定代碼可以訪問哪些變量、函數、對象等。

我們將執行上下文簡單視爲運行當前代碼的 environment / scope,我們知道作用域分爲 global scopelocal scope

類似的,執行上下文也分爲不同的類型:

全局執行上下文 - 代碼首次執行時候的默認環境,在代碼的整個執行過程中,只用一個全局執行上下文。

函數執行上下文 - 每當執行流程進入到一個函數體內部的時候,就會創建一個函數執行上下文,可以有任意數量的函數執行上下文。

clipboard.png

執行棧/調用棧

JavaScript 是單線程的,瀏覽器只分配給 JavaScript 一個主線程,一次只能執行一個任務(函數),因此它在執行棧中對其他操作(事件和函數執行)形成一個任務隊列,排隊等候執行。

clipboard.png

每當在瀏覽器中加載腳本時,棧 stack 中的第一個元素就是全局執行上下文。當有函數執行時,將創建一個函數執行上下文,並將其置於全局執行上下文之上。一旦函數執行完成,它就會從執行堆棧中彈出,並將控制權交給它下面的上下文中。結合上面說到的,我們看一個例子:

var name = "global variable";
console.log(name)

function func1() {
  console.log("func1 被調用了。")
  func2();
}
function func2() {
  console.log("func2 被調用了。");
}
func1();

clipboard.png

當上述代碼在瀏覽器中加載時:

  • Javascript 引擎創建一個全局執行上下文 global execution context 並將其推送到當前執行棧。
  • 接着進行 func1() 被調用,然後 Javascript 引擎爲該函數創建一個新的函數執行上下文 function execution context 並將其推送到全局執行上下文的頂部。
  • 在執行 func1() 過程中,發現 func2() 被調用,Javascript 引擎爲該函數創建一個新的執行上下文,並將其推送到 func1() 執行上下文的頂部。
  • func2() 函數完成時,其執行上下文從當前堆棧彈出,將控制權交給其下面的執行上下文,即 func1() 函數執行上下文。
  • func1() 完成後,其執行堆棧將從堆棧中刪除,將控制權交給全局執行上下文。一旦執行了所有代碼,JavaScript 引擎就會從當前堆棧中刪除全局執行上下文。

執行上下文階段

執行上下文主要有兩個階段:創建階段執行階段,接下來我們將逐一進行介紹。

創建階段

在函數執行發生之前,JavaScript 引擎會做如下事情:

  • 首先,爲每個函數或變量創建與外部環境的連接,這些函數形成作用域鏈 scope chain。作用鏈告訴執行上下文它應該包含什麼,以及它應該在哪裏查找解析函數的引用和變量的值。(對於全局環境,外部環境爲 null。在全局作用域內的所有環境都將把全局環境作爲其外部環境)。
  • 掃描作用鏈後,將創建一個環境存儲器,其中全局上下文的創建和引用(Web瀏覽器中的窗口),變量、函數和函數參數的創建和引用在內存中完成。
  • 最後,在第一步中創建的每個執行上下文中確定 this 關鍵字的值(對於全局執行上下文,this 指向 window)。

我們可以將創建階段使用僞代碼這樣表示:

creationPhase = { // 創建階段
  'outerEnvironmentConnection': { // 創建外部連接
        // 形成作用域鏈
   },    
   'variableObjectMapping': {
        // 變量、函數和函數參數的創建和引用在內存中完成。
   },
   'valueOfThis': {},  // 確定 this 的值
}

執行階段

這是代碼在創建階段形成的執行上下文中的運行的階段,並且逐行分配變量值。

當執行開始時,JavaScript 引擎在其創建階段對象中查找執行函數的引用。如果在當前對象中沒有找到,它將沿着作用域鏈繼續向上查找,直到它到達全局環境。

如果在全局環境中找不到函數引用,則將返回錯誤。如果找到了引用並且函數正確執行,那麼這個特定函數的執行上下文將從棧中彈出,接着 JavaScript 引擎將移動到下一個函數,它們的函數執行上下文將被加入到棧中並執行,以此類推。

讓我們通過示例來看看上面的兩個階段,以便更好地理解它。

let name = "webinfoq";
var title = "execution context";
const message = "hello world";

function func1(num) {
  var author = "deepak";
  let value = 3;
  let func2 = function multiply() {
    return num * value;
  }
  const fixed = "Divine";
  function addFive() {
    return num + 5;
  }
}
func1(10);

因此,全局執行上下文將如下表示:

globalExecutionObj = {  // 全局執行s上下文
    outerEnvironmentConnection: null,  // 全局上下文外部環境爲 null
    variableObjectMapping: { 
        name: uninitialized,  // 在創建階段,let聲明的變量是未初始化狀態
        title: undefined,   // var 聲明的變量表示爲未定義
        date: uninitialized, // 在創建階段,const聲明的變量是未初始化狀態
        func1: <func1 reference>,  func1 地址引用
    },
    this: window //Global Object  
}
注意:letconst 定義的變量在創建階段沒有任何與之關聯的值,但 var 定義的變量在創建階段爲 undefined
這就是爲什麼可以在 var 聲明之前訪問變量,(得到的是 undefined), 在 letconst
聲明之前訪問會報錯(暫時性死區)。

這就是所謂的變量提升,所有使用 var 聲明的變量都會被提升到作用域的頂部。

執行階段,完成對變量的賦值等操作。因此,在執行階段,全局執行上下文global execution 看起來像這樣:

globalExectutionObj = {  // 全局執行上下文
    outerEnvironmentConnection: null,
    variableObjectMapping: {
        name: "webinfoq",
        title: "execution context",
        message: "hello world",
        func1: pointer to function func1, // 指向func1的指針
    },
    this: window //Global Object
}

當執行到 func1() 時,將形成新的函數執行上下文 function execution global,創建階段如下所示:

func1ExecutionObj = {  // func1 函數執行上下文
    outerEnvironmentConnection: Global,  // 外部環境爲全局環境
    variableObjectMapping: {
       arguments: {
            0: 10,
            length: 1
        },
        num: 10,
        author: undefined,  // var 聲明的
        value: uninitialized,  // let 聲明的
        func2: uninitialized,  // let 聲明的
        fixed: uninitialized,  // const 聲明
        addFive: pointer to function addFive()  // 指向函數addFive的指針
    },
    this: Global Object or undefined  
}

執行階段:

func1ExecutionObj = {
    outerEnvironmentConnection: Global,  
    variableObjectMapping: {
       arguments: {  // 先處理 arguments 參數
            0: 10,
            length: 1
        },
        num: 10,
        author: "deepak",  //變量f賦值
        val: 3,
        func2: pointer to function func2() 
        fixed: "Divine"
        addFive: pointer to function addFive()
    },
    this: Global Object or undefined
}

Javascript 引擎創建執行上下文調用棧。當有函數執行時,引擎就會創建一個新的函數執行上下文。最後所用函數執行完成後,將更新全局環境,然後全局代碼完成,程序結束。

瞭解更多請關注微信公衆號:webinfoq

clipboard.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章