這是一張簡單的JavaScript執行圖(如有錯誤地方請指出,謝謝大家)。大致分為兩個階段,編譯階段和執行階段。在上一篇文章【JavaScript變數提升執行機制】中有簡單提到過。這篇文章帶大家來了解其中的一些概念。
一、編譯階段
分詞/詞法分析
這個過程是將由字元組成的字串分解為有意義的程式碼塊,這些程式碼塊我們稱之為詞法單元(token)。例如:var num = 1;在當前階段會被分解為var、num、=、1、空格,每一個都是一個詞法單元,當然空格是否是一個有效的詞法單元取決於空格是否在JavaScript是否有意義。
解析/語法分析
這個過程主要是將詞法單元流(陣列)轉換為一個由元素巢狀的程式語法樹(抽象語法樹,AST)。
預解釋/程式碼生成
這個階段主要是將AST轉換為可執行程式碼。這個階段會進行變數的提升。
引入幾個簡單的概念:
引擎:負責整個JavaScript的編譯和執行過程。編譯器:負責JavaScript的語法分析和程式碼生成。作用域:負責收集並維護所有申明的識別符號組成的一系列查詢,並實施一套規則,確定當前執行的程式碼對這些識別符號的訪問許可權。複製程式碼
二、執行階段
1、可執行程式碼
JavaScript並不是簡單的一行行解釋執行,而是將JavaScript程式碼分為一塊塊的可執行程式碼塊進行執行,JavaScript中主要主要分為三類可執行程式碼。
- 全域性可執行程式碼
- 函式可執行程式碼
- Eval可執行程式碼
2、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);
}複製程式碼
當程式碼進行宣告時
執行sayName函式時,會把直接函式壓如執行棧,並且會建立執行上下文
4、執行上下文
JavaScript中每一個可執行程式碼,在解釋執行前,都會建立一個可執行上下文。按照可執行程式碼塊可分為三種可執行上下文
- 全域性可執行上下文:每一個程式都有一個全域性可執行程式碼,並且只有一個。任何不在函式內部的程式碼都在全域性執行上下文。
- 函式可執行上下文:每當一個函式被呼叫時, 都會為該函式建立一個新的上下文。每個函式都被呼叫時都會建立它自己的執行上下文。
- Eval可執行上下文:Eval也有自己執行上下文。
5、執行上下文的建立
- this的指向:除開箭頭函式的this是編輯階段確定的之外,其他this都是在程式碼執行階段【程式碼執行階段 == 執行上下文建立階段】確認的。
1、普通函式的呼叫:this指向window(瀏覽器環境)
2、物件方法的呼叫:this指向呼叫物件
3、建構函式:this指向建構函式例項
4、apply、call、bind:this指向繫結值
5、箭頭函式this:this指向外層第一個普通函式呼叫的this複製程式碼
- 建立詞法環境
- 全域性環境:全域性環境的外部環境引用是 null,它擁有內建的 Object/Array/等、在環境記錄器內的原型函式(關聯全域性物件,比如 window 物件)還有任何使用者定義的全域性變數,並且 this的值指向全域性物件。
- 模組環境:包含模組頂級宣告的繫結以及模組顯式匯入的繫結。 模組環境的外部環境是全域性環境。
- 函式環境:函式內部使用者定義的變數儲存在環境記錄器中,外部引用既可以是其它函式的內部詞法環境,也可以是全域性詞法環境
詞法環境本身包括兩個部分:
- 『環境記錄器(Environment Record)』是儲存變數和函式宣告的實際位置
- 『外部環境的引用(outer Lexical Environment)』指它可以訪問其父級詞法環境(即作用域)
對於『環境記錄器』而言,它又分為兩個主要的環境記錄器型別:
- 宣告式環境記錄器(DecarativeEnvironmentRecord):範圍包含函式定義,變數宣告,try...catch等,此型別對應其範圍內包含的宣告定義的識別符號集
- 物件式環境記錄器(ObjectEnvironmentRecord):由程式級別的(Program)物件、宣告、with語句等建立,與稱為其繫結物件的物件相關聯,此型別對應於其繫結物件的屬性名稱的字串識別符號名稱集
- 建立變數環境:變數環境也是一個詞法環境,但不同的是詞法環境被用來儲存函式宣告和變數(let 和 const)繫結,而變數環境只用來儲存 var 變數繫結。
6、程式碼執行
當瀏覽器載入某些JavaScript程式碼時,引擎會逐行讀取並執行以下步驟:
- 使用變數和函式宣告填充全域性記憶體(堆)
- 將每個函式呼叫推送到呼叫堆疊
- 建立全域性執行上下文,其中執行全域性函式
- 建立許多微小的本地執行上下文(如果有內部變數或巢狀函式)
三、小結
通過這個文章我們可以簡單的瞭解相關的JavaScript程式碼執行機制。