JavaScript的程式碼執行機制

蝸牛的北極星之旅發表於2019-09-16

JavaScript的程式碼執行機制

這是一張簡單的JavaScript執行圖(如有錯誤地方請指出,謝謝大家)。大致分為兩個階段,編譯階段和執行階段。在上一篇文章【JavaScript變數提升執行機制】中有簡單提到過。這篇文章帶大家來了解其中的一些概念。

一、編譯階段

分詞/詞法分析

這個過程是將由字元組成的字串分解為有意義的程式碼塊,這些程式碼塊我們稱之為詞法單元(token)。例如:var num = 1;在當前階段會被分解為var、num、=、1、空格,每一個都是一個詞法單元,當然空格是否是一個有效的詞法單元取決於空格是否在JavaScript是否有意義。

解析/語法分析

這個過程主要是將詞法單元流(陣列)轉換為一個由元素巢狀的程式語法樹(抽象語法樹,AST)。

預解釋/程式碼生成

這個階段主要是將AST轉換為可執行程式碼。這個階段會進行變數的提升

引入幾個簡單的概念:

引擎:負責整個JavaScript的編譯和執行過程。編譯器:負責JavaScript的語法分析和程式碼生成。作用域:負責收集並維護所有申明的識別符號組成的一系列查詢,並實施一套規則,確定當前執行的程式碼對這些識別符號的訪問許可權。複製程式碼

二、執行階段

1、可執行程式碼

JavaScript並不是簡單的一行行解釋執行,而是將JavaScript程式碼分為一塊塊的可執行程式碼塊進行執行,JavaScript中主要主要分為三類可執行程式碼。

  • 全域性可執行程式碼
  • 函式可執行程式碼
  • Eval可執行程式碼

2、JavaScript引擎

JavaScript的程式碼執行機制

上圖描述了JavaScript的執行過程,具體過程我們先不看。一個JS引擎主要有一下幾部分組成。

  • 編譯器:負責JavaScript的語法分析和程式碼生成。
  • 解析器:在某些引擎中,直譯器主要是接收位元組碼,解釋執行這個位元組碼,同時也依賴垃圾回收機制等。
  • JIT:將字碼節或者抽象語法樹轉換為本地可執行程式碼。
  • 垃圾回收,分析工具:負責垃圾回收和收集引擎中的資訊,幫助改善引擎的效能和功效。

3、JavaScript引擎的記憶體堆(emory Heap)和呼叫棧(Call Stack)

記憶體堆(emory Heap):分配記憶體地址

呼叫棧(Call Stack):程式碼執行

    let name = '蝸牛';

    function sayName(name) {
        sayNameStart(name);
    }
    function sayNameStart(name) {
        sayNameEnd(name);
    }
    function sayNameEnd(name) {
        console.log(name);
    }複製程式碼

當程式碼進行宣告時

JavaScript的程式碼執行機制

執行sayName函式時,會把直接函式壓如執行棧,並且會建立執行上下文

JavaScript的程式碼執行機制

JavaScript的程式碼執行機制

4、執行上下文

JavaScript中每一個可執行程式碼,在解釋執行前,都會建立一個可執行上下文。按照可執行程式碼塊可分為三種可執行上下文

  • 全域性可執行上下文:每一個程式都有一個全域性可執行程式碼,並且只有一個。任何不在函式內部的程式碼都在全域性執行上下文。
  • 函式可執行上下文:每當一個函式被呼叫時, 都會為該函式建立一個新的上下文。每個函式都被呼叫時都會建立它自己的執行上下文。
  • Eval可執行上下文:Eval也有自己執行上下文。


5、執行上下文的建立

  • this的指向:除開箭頭函式的this是編輯階段確定的之外,其他this都是在程式碼執行階段【程式碼執行階段 == 執行上下文建立階段】確認的。

1、普通函式的呼叫:this指向window(瀏覽器環境)
2、物件方法的呼叫:this指向呼叫物件
3、建構函式:this指向建構函式例項
4、applycallbindthis指向繫結值
5、箭頭函式thisthis指向外層第一個普通函式呼叫的this複製程式碼

  • 建立詞法環境
  1. 全域性環境:全域性環境的外部環境引用是 null,它擁有內建的 Object/Array/等、在環境記錄器內的原型函式(關聯全域性物件,比如 window 物件)還有任何使用者定義的全域性變數,並且 this的值指向全域性物件。
  2. 模組環境:包含模組頂級宣告的繫結以及模組顯式匯入的繫結。 模組環境的外部環境是全域性環境。
  3. 函式環境:函式內部使用者定義的變數儲存在環境記錄器中,外部引用既可以是其它函式的內部詞法環境,也可以是全域性詞法環境

詞法環境本身包括兩個部分:

  1. 『環境記錄器(Environment Record)』是儲存變數和函式宣告的實際位置
  2. 『外部環境的引用(outer Lexical Environment)』指它可以訪問其父級詞法環境(即作用域)

對於『環境記錄器』而言,它又分為兩個主要的環境記錄器型別:

  1. 宣告式環境記錄器(DecarativeEnvironmentRecord):範圍包含函式定義,變數宣告,try...catch等,此型別對應其範圍內包含的宣告定義的識別符號集
  2. 物件式環境記錄器(ObjectEnvironmentRecord):由程式級別的(Program)物件、宣告、with語句等建立,與稱為其繫結物件的物件相關聯,此型別對應於其繫結物件的屬性名稱的字串識別符號名稱集
  • 建立變數環境:變數環境也是一個詞法環境,但不同的是詞法環境被用來儲存函式宣告和變數(let 和 const)繫結,而變數環境只用來儲存 var 變數繫結。


6、程式碼執行

當瀏覽器載入某些JavaScript程式碼時,引擎會逐行讀取並執行以下步驟:

  • 使用變數和函式宣告填充全域性記憶體(堆)
  • 將每個函式呼叫推送到呼叫堆疊
  • 建立全域性執行上下文,其中執行全域性函式
  • 建立許多微小的本地執行上下文(如果有內部變數或巢狀函式)


三、小結

通過這個文章我們可以簡單的瞭解相關的JavaScript程式碼執行機制。

參考:JavaScript的執行機制

                           JavaScript的程式碼執行機制


相關文章