iT邦幫忙

2022 iThome 鐵人賽

DAY 2
3

前言

在這篇文章中,將會介紹 JS 是如何透過 JavaScript Engine 做轉換,變成電腦讀的懂並可以執行的機器語言以及介紹 JS 引擎內部幾個優化其執行速度的機制。


為什麼需要 JS 引擎?

因為 JS 是一種高階的程式語言,它不能直接給電腦執行,所以需要透過 JS 引擎將 JS 轉換成更為底層的機器語言交給電腦去執行。

那麼 JS 引擎存在於哪裡呢?答案是瀏覽器內部,不過因為瀏覽器有很多種,所以每家瀏覽器也推出了自己的 JS 引擎,例如 Google's Chrome 使用 V8、Safari 使用 JavaScriptCore、 Firefox  使用 SpiderMonkey。

而目前大部分 JS 引擎使用了合併 Interpreter & Compiler 兩者優點的 JIT Compiler(Just-in-time compiler),更加提升了 JS 執行速度。


JavaScript V8 Engine 運作過程

在初步知道 JS 引擎後,我們來了解它的運作過程,因為每個 JS 引擎實際上轉換程式碼的步驟並不一定完全相同,故以下步驟以 V8 為舉例。

第 1 步: 使用 Parser 進行語法解析並轉換成抽象語法樹(AST,Abstract Syntax Tree)

首先 JS 引擎的某個模組 "Parser" 會將 JS 語法做解析,在 AST Explorer 網站上,可以看到網站預設的範例,如左邊紅框的 tips 變數,經過解析後會變成右邊紅框的樣子,而右邊整個的程式內容就稱為 AST。

第 2 步: 透過 Interpreter(直譯器)將 AST 轉換成 ByteCode

V8 內部的直譯器會一行行地將 AST 轉換成 ByteCode,而 ByteCode 部分需要優化的 ByteCode(如重複執行的程式碼)會交給 Compiler 做優化。

如果想看 ByteCode 長什麼樣子,可以選一個 JS 檔並輸入以下指令執行:

node --print-bytecode 檔名.js

補充: node.js 執行也是使用 V8 引擎

第 3 步: 進一步優化,Profiler & Compiler(編譯器)

在 V8 內部,多次且常執行的函式可能會進入到這個步驟,這些將被優化的 ByteCode 會透過 Compiler 轉換成 Machine Code。

如果想看 Compiler 編譯完的程式碼,可以選一個 JS 檔並輸入以下指令執行:
node --print-code --print-opt-code 檔名.js


JavaScript Engine 優化自己執行速度的機制

JS Engine 為了提升執行速度,在內部設計了一些機制,而以下提到的兩個機制有借鑑到了靜態語言的特性。

Hidden classes(Shapes)

目的: 提升物件屬性的查詢速度

在靜態語言中,建立物件之前會先建立好 class 並用 new 關鍵字去產生物件,而在物件建立好後,其每個屬性都會儲存一份記憶體偏移量,因此可以直接透過"偏移量"去取得屬性值。

而在 JS 引擎中,JS 物件的各個屬性是以下圖的樣子在記憶體做儲存的,分為 Named properties & Array-indexed properties 兩個像陣列或是字典的資料結構去儲存,再根據物件的使用情境去決定要用哪邊的方式去取屬性值,讓存取和修改屬性更有效率。

圖片改編自 V8 官網的文章

在 V8 執行過程中,會假設 JS 的物件是靜態的,也就是物件建立好後不會去增加新屬性,也不會刪除屬性。

Hidden classes 會記錄物件的一些資訊,例如上圖物件在記憶體的樣子、物件原型的參考、物件屬性數量等。

每當物件新增或是刪除屬性時,會改變 Hidden classes,而如果兩個物件的屬性和值都完全相同時,會共用同一個 Hidden classes,減少記憶體使用空間。

由上面的 Hidden classes 特性可以知道幾個讓效能更好的小技巧(即使我們感覺不出來XD):

  1. 一開始就定義好物件屬性,如果之後額外增加屬性會改變 hidden class
  2. 如果確定會為物件增加多個屬性,那麼後面產生的物件 B 也要按照第一個物件 A 增加屬性順序去增加,減少 hidden class 產生

若讀者有興趣進一步了解 Hidden classes 的運作過程,可以參考這篇文章

Inline caching

Inline caching 的目的是加速運算,在 V8 引擎內部如果多次用到同一個 Hidden class 並取得相同的屬性值,就會透過 Inline caching 機制進行暫存,當下次又使用到相同的物件屬性值時,將會直接取得屬性值,跳過了從Hidden classes 查找屬性的過程。

ex:

function findUser(user) {
  return `found ${user.firstName} ${user.lastName}`;
}

const userData = {
  firstName: 'John',
  lastName: 'Lin',
}

findUser(userData); // `found John Lin
findUser(userData); // 底層運作會直接傳 `found John Lin,不查找 useData 的屬性值
findUser(userData); // 直接傳 `found John Lin,不查找 useData 的屬性值

JavaScript Engine 的工作 & 內部有什麼

實際上 JS 引擎的任務不只是要編譯程式碼,它還要負責執行程式碼、分配記憶體以及垃圾回收。因此在這個段落要來介紹 JS 引擎內部的兩個東西,Call Stack & Memory Heap 以及一些相關名詞。

Call Stack

Call Stack 可以用來記錄目前程式執行到哪裡,在 JS 執行的過程中,會將要執行的程式碼依照執行的先後順序加入到 Call Stack 裡面,並且它是一個堆疊的資料結構。

JS 是一個 Single Threaded 單線程的程式語言,也就是只有一個 Call stack

以下為範例:

function addTwoNums(x, y) {
  return x + y;
}

function showSum(x) {
  const sum = addTwoNums(x, x);
  console.log(sum);
}

showSum(5);

另外在 Chrome devtool 也可以在 Source 看到 Call Stack,有興趣的讀者可以自行打開來看。

Stack Overflow

Call Stack 是有限制的,如果在 Stack 內執行的任務不斷的堆積而沒有 pop 出去,就會造成 Stack Overflow。

Stack Overflow 範例,一直呼叫 inception 函式。

function inception() {
  inception();
}

inception();

Memory Heap

用來配置記憶體和儲存資料,可以想像 Memory Heap 內有很多個格子,然後把一筆筆資料存入格子內,格子上有 address,透過 address 就可取出格子內的資料。

Garbage Collection(GC)

當建立變數、函式時都會配置記憶體去儲存該變數的資料,而在 V8 引擎中,有個 garbage collector,它會從記憶體內將用不到的資料回收。

不過即使 JS 這個語言會自動幫你做 GC,雖然可以不用撰寫釋放記憶體的程式碼,但 JS 沒有主動釋放記憶體的 API,若程式沒有寫好可能會有 Memory Leaks 的問題。

Memory Leaks

若存在 Memory Heap 的資料已經不會用到了,但卻一直沒有被 GC,就會導致 Memory Leaks。


關於 Garbage Collection & Memory Leaks,礙於篇幅關係,在明天的文章將會有更詳細的介紹。

參考資料 & 推薦閱讀

How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code

教女朋友学前端之深入理解JS引擎

JavaScript深入浅出第4课:V8引擎是如何工作的?

浏览器是如何工作的:Chrome V8让你更懂JavaScript


上一篇
Day1-系列大綱 & 前言
下一篇
Day3-JavaScript 記憶體管理
系列文
強化 JavaScript 之 - 程式語感是可以磨練成就的30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

1
json_liang
iT邦研究生 5 級 ‧ 2022-09-02 10:09:46

感謝大大分享關於 js engine 的機制,讓我能夠理解底層的運作原理。受益良多!

harry xie iT邦研究生 1 級 ‧ 2022-09-04 10:50:26 檢舉

謝謝支持!

0
janlin002
iT邦好手 1 級 ‧ 2023-02-23 14:47:12

感謝分享,學到了~~

harry xie iT邦研究生 1 級 ‧ 2023-02-23 15:35:47 檢舉

謝謝支持!

我要留言

立即登入留言