JavaScript 是如何運行的

當我輸入這行程式碼,電腦會如何去運行這行程式碼呢?

1
var ming = '小明';

其實,電腦、瀏覽器無法直接閱讀我們所撰寫的 JS

💡 電腦閱讀程式碼的邏輯,跟「我撰寫程式碼的邏輯」是不同的因此,在運行程式碼前,要先經過「編譯 or 直譯」

直譯式語言(Interpreted language)

🎃 JavaScript 是屬於「直譯式語言」–> 在執行之前,沒有經過編譯

📖 「直譯式語言」的運作邏輯:

  1. 將原始碼透過「直譯器」,直接「生成代碼」
  2. 生成代碼後,就可以直接運行了

💡 電腦無法直接看懂「我所撰寫的原始碼」,電腦需要透過「直譯器」來閱讀我所寫的原始碼


‼️ 「直譯式語言」–> 錯誤會直接反映在「執行環境」中

因為 JS 不會預先編譯(不會“預先除錯”),因此「JS 的錯誤都會直接呈現在 Console 上」

「直譯式語言」的優點

📍 優點:更有彈性、不需要「預先定義型別」

  1. 先將語法「基本單元化」:把 JS 語法中的「詞彙、標點符號」一一解析出來(轉為 token)
  2. 把「一個一個的 token」轉為「抽象結構樹」,將整個「原始碼的結構」定義出來 (此時,還沒有執行程式碼)
  3. 最後,再將代碼生成出來 (代碼生成出來後,才會運行程式碼)

步驟一:語法基本單元化(Tokenizing)

🔧 直譯網站 Esprima

📖 「token」是「標誌;表示;象徵」的意思(noun)
–> 「Tokenizing」就是:將一個一個字詞都轉成「token」

1
var ming = '小明';

當我輸入這行程式碼,電腦不會直接了解到「我現在要宣告一個變數,叫做’小明’」,而是直接先將「各個字詞的意思」一一解析出來

那麼,電腦到底是如何解析這行程式碼呢?

就好像小時候學寫作文,只是先把結構取出來

📖 Esprima 這個工具可以呈現出原始碼在「基本單元化」之後的結構

把這行var ming = '小明';貼到左側框框內後,在右側選擇「Tokens」頁籤,就可以看到,它把詞彙一一解析出來的結果:

1️⃣ 辨識出var是一個「關鍵字」

1
2
3
4
{
"type": "Keyword",
"value": "var"
},

2️⃣ 電腦不會知道ming是一個「變數」,電腦只知道ming是一個「被 user 定義出來的文字」

1
2
3
4
{
"type": "Identifier",
"value": "ming"
},

3️⃣ 電腦不會知道=是用來「賦予值的」,電腦只知道=是一個「標點符號」

1
2
3
4
{
"type": "Punctuator",
"value": "="
},

4️⃣ 電腦不會知道'小明'是代表什麼意思,電腦只知道'小明'是一個「字串」

1
2
3
4
{
"type": "String",
"value": "'小明'"
},

5️⃣ 電腦不會知道;是用來「做為語句的結尾」,電腦只知道;是一個「標點符號」

1
2
3
4
{
"type": "Punctuator",
"value": ";"
}

步驟二:轉為「抽象結構樹」

接下來,電腦會把「剛剛解析出來的 token」,轉為電腦看得懂的「抽象結構樹」

點擊「Tree」頁籤,就可以看到「抽象結構樹」,它類似於 JSON 的格式

‼️ 一直到「定義出抽象結構樹」這個步驟,電腦才知道「現在正在定義一個變數」,但是「還沒有執行」定義變數的動作(真正的執行是在“代碼生成”之後)

🎃 「VariableDeclarator」代表:var ming = '小明'; 這行程式碼主要就是要「定義一個變數」
🎃 「name: ming」代表:user 自訂的變數字詞
🎃 「value: 小明」代表:把「小明」這個詞彙,賦予到變數上
🎃 最下方有個「kind: var」代表:定義變數所使用的方法是「var」

步驟三:代碼生成

🎃 會因為執行環境不同,生成的代碼都不一樣

執行環境有可能是:瀏覽器 或是 Node.js


如果將程式碼改成這樣寫:

1
ming = '小明';

透過「抽象結構樹」,可以看到,這樣寫的話,會跟var ming = '小明';是完全不一樣的意思
🎃 「AssignmentExpression」代表:是一個「assign 的表達式」–> 把「'小明'這個值」直接賦予(指派)在「變數ming」上

‼️ 根本就沒有做「宣告」

編譯式語言

📖 「編譯式語言」的運作邏輯:

🎃 會「預先編譯」,編譯完成後,才執行

  1. 先撰寫完「原始碼」
  2. 把原始碼「預先編譯」後,就會直接「生成代碼」
  3. 把代碼丟到「執行環境」去運行

「編譯式語言」的優點

✔️ 預先編譯,就可以「預先除錯」–> 執行效能較佳