【課程筆記】JS#2:JavaScript引擎與JavaScript的運行環境 ─ 什麼?JavaScript早就不只是直譯式語言了?!(feat:JavaScript Engine、JavaScript Runtime)

 

JavaScript,JavaScript_Engine,JavaScript_Runtime,JUST_IN_TIME_Compilation,jonas_schmedtmann

這一系列文章,是在Udemy上參與 Jonas  Schmedtmann的課程-The Complete JavaScript Course 2021: From Zero to Expert!所做的學習筆記,為了深入理解JavaScript運作原理,記下那些我未曾注意過的JavaScript細節。內容若有任何錯誤,歡迎留言交流指教!


Outline
- JavaScript引擎是什麼?(What is JavaScript Engine?)
-機器如何讀懂程式語言─直譯式 VS 編譯式 (Computer Science Sidenote:Compilation VS. Interpretation)
-現代JavaScript 即時編譯語言 (Modern JUST-IN-TIME Compilation of JavaScript)
-JavaScript的運行環境 The Bigger Picture:JavaScript Runtime

§ JavaScript引擎是什麼?§

(What is JavaScript Engine?)

說到JavaScript原理,我們必須先了解JavaScript到底是如何被執行的,而這就必須先談到─JavaScript引擎

JavaScript引擎是一個專門處理JavaScript指令碼的虛擬機器,一般會附帶在網頁瀏覽器之中。而每個瀏覽器都有自己的JavaScript引擎,如 Chrome 的 v8,或是 Firefox 的 SpiderMonkey,而其中最著名的當然就是Google的V8引擎,V8為Google Chrome與node.js提供動力。

而所有的JavaScript引擎都是主要由兩個元件組成:

1.Call Stack (執行堆疊、堆疊棧)
2.Memory Heap (內存堆)

Call Stack是程式碼實際執行的地方,透過各程式碼的執行環境(execution contexts)依序安排執行。
Memory Heap是一個非結構化的內存池,這裡儲存了我們的應用程序所需要的所有對象。

而這兩個重要元件及實際執行的細節,會在下篇筆記詳細介紹,因為那就是JavaScript的核心原理。
現在我們大概知道程式碼是在哪裡被執行後,讓我們更進一步談談程式碼是如何在電腦或瀏覽器上運作的?

§ 機器如何讀懂程式語言 §

首先,我們都知道電腦只看得懂機器語言,也就是 0 與 1 。但我們寫這麼多的英文(所謂的抽象語言)又是怎麼被電腦所理解呢?沒錯,當然就是需要經過層層翻譯。而這個擔當我們的語言與電腦機器語言間的翻譯工具,我們稱它為編譯器(Compiler)。

編譯器(Compiler)是一種電腦程式,主要目的是將我們寫成的高階語言(原始程式 source program),翻譯輸出成電腦能解讀、執行的目標語言(target language),再打包成執行檔給電腦執行。

而通常現代編譯器的主要工作流程如下:

原始碼(source code)→ 預處理器(preprocessor)→ 編譯器(compiler)→ 組譯程式(assembler)→ 目的碼(object code)→ 連結器(linker)→ 執行檔(executables),最後打包好的檔案就可以給電腦去判讀執行。

對編譯器有大致的理解後,讓我們再來談談編譯的方法,其中大致可以分為編譯語言 (Compiled Language) 和直譯語言 (Interpreted Language),與結合這兩種語言的即時編譯語言(Just-In-Time Compilation)

 § 直譯式語言 VS 編譯式語言 § 

(Compiled Language VS. Interpreted Language)


▽ Compilation 編譯式語言 (Compiled Language)


一次性將整個原始碼編譯成為機器語言,並寫成可以在機器上執行的二進制文件檔,然後交由電腦執行。而編譯式語言大多都是靜態語言(static language),具有事先定義型別、型別檢查 (type check) 與擁有高效能的執行速度等特性。

編譯流程:

source code → 編譯 → machine code (portable file可攜式檔案) → 執行 → program running
例如:C、C++、bjective-C、Visual Basic

▽ Interpretation 直譯式語言 (Interpreted Language)

相較編譯式語言,直譯式語言就像是一個即時口譯員員,不是將整份文件一次性翻譯,而是由上到下,由左到右,在要執行時,才將程式碼逐行翻譯時同步逐行執行,也就是說讀取與執行是同步發生的,不像編譯式語言是「在執行前,提前翻譯完成」,而當然相較提前一次翻好直接執行的編譯式語言,直譯式語言的速度是相對較慢的,而通常這類型的語言也多半以動態語言(dynamic language)為主,具有靈活的型別處理、動態生成與程式彈性。

編譯流程:

source code → 編譯同時讀取執行 line by line → program running

例如:Python、Ruby,而JavaScript也曾經被歸類為直譯式語言

差異:直譯式語言 VS 編譯式語言 

◆ 執行速度:編譯式語言 > 直譯式語言

由上述流程可見,一般來說編譯語言已經在執行前預先編譯完成整份文件,在執行期上,少了直譯式語言逐行翻譯的等待時間,當然執行效率就會高於直譯式語言。

◆ 開發效率、除錯速度:編譯式語言 < 直譯式語言

同理,因為編譯語言是整份原始碼完整翻譯的原因,無法像直譯式語言,開發完一段程式碼就馬上執行及除錯,所以對於人類(開發人員)在開發效率與除錯速度而言,直譯式語言在整體的開發彈性上是優於編譯式語言的。

§ 現代JavaScript 即時編譯語言 §
(Modern JUST-IN-TIME Compilation of JavaScript)


曾經JavaScript是一個純粹的直譯式語言,但因為執行效率與編譯式語言落差太多,以及日漸成熟的網路應用程式,低效能已經是一件不能被接受的事情。(一言不合就很容易讓人關掉網頁,對吧?)

▽  Just-In-Time Compilation 即時編譯(混合使用編譯與直譯)

現代JavaScript是採即時編譯法,而這個方法簡單來說,就是先一次性編譯整個原始碼,然後立即執行,與編譯式語言相同的步驟為「提前編譯」,但沒有生成文件,而是直接執行。

編譯流程:
source code → 編譯 → machine code(沒有可移植文件) → 執行line by line(多次優化) → program running

等等,真的這麼簡單嗎?那為什麼還這麼多人認為JavaScript是直譯式語言?
現在讓我們進一步了解,JavaScript到底是怎麼做到這件事的─

JavaScript,JavaScript_Engine,JavaScript_Runtime,JUST_IN_TIME_Compilation,jonas_schmedtmann
(JUST-IN-TIME Compilation of JavaScript from:Jonas Schmedtmann )

JavaScript原始碼進入引擎 → 解析(Parsing) → 轉為數據結構,組成抽象語法樹(AST-abstracr syntax tree)(註1) → 編譯(Compilation) → AST轉換為machine code並立即被執行(註2) → program running

註1:在這個步驟,是先將每一行代碼拆分為對程式語言而言有意義的片段,例如const、function等關鍵字,並將這些所有片段以結構化的方式生成抽象語法樹(AST)。並同時在這個步驟會檢查是否有語法錯誤的情形。(如上圖右側)

*AST abstracr syntax tree 與DOM Tree沒有任何關係,只是引擎內的整個代碼的表示方式。DOM是一個文件(樹)的結構化表示法也稱為文件物件模型,是一個定義讓程式可以存取並改變文件架構、風格和內容的方法。

現代JavaScript引擎高效執行策略

註2:為了讓程序可以盡快開始執行,JavaScript引擎首先會創建一個未優化的machine code並直接開始執行,而在執行期間,這段代碼也在後台重新編譯、持續優化,並在每次優化後,未優化的代碼都會被掃除,且新的更優化的代碼也不會停止執行,如此反覆調用、堆棧執行代碼,而這就是V8之所以能運行如此快速的原因。

而這一整個解析、編譯、優化的過程發生於引擎內部的一些特殊線程中,是我們無法從程式碼中訪問的,也就是說這些過程與我們在執行堆疊(call stack)中執行的主要線程程式碼是完全分開來的。

雖然整體實現方式會因為不同的瀏覽器所搭載的不同JavaScript引擎而略有不同,但現代版JavaScript的編譯方式皆類似於此。

而既然談到瀏覽器,最後讓我們順帶介紹一下JavaScript的運行環境。

§ JavaScript的運行環境  JavaScript Runtime §

JavaScript的運行環境-(在瀏覽器內in the browser)

JavaScript,JavaScript_Engine,JavaScript_Runtime,JUST_IN_TIME_Compilation,jonas_schmedtmann
(JavaScript Runtime in the browser from:Jonas Schmedtmann )

首先,為了避免混淆運行環境的原件都是JavaScript的一部份,或把所有元件混為一談,先讓我們單純想像JavaScript的運行環境是一個容器,裡面裝有所有運行JavaScript所需要的物件。

◆ JavaScript引擎

首先,要運行JavaScript當然需要JavaScript引擎(而這又會帶出兩個主要元件Heap、Call Stack如圖),這也是為什麼在這篇筆記中,反覆圍繞著JS引擎,因為沒有了JS引擎,就沒有辦法運行JS了。

◆ Web APIs

為了能夠正常運作,我們還需要訪問Web APIs,而Web APIs就是與DOM有關的一切,例如setTimOut、setInterval、document,甚至console.log等,由各家瀏覽器所提供。

簡單來說,Web API就是一個提供給引擎能夠訪問物件的功能,但並不屬於JavaScript引擎的一部分。JavaScript只是需要通過訪問這些API才能接觸全局窗口物件。

◆ Callback Queue 回調列隊

這是一個數據結構包含了所有正準備被執行回調函式(callback function),說起來好像很複雜,但例如我們常使用的滑鼠點擊事件就是一個回調函式

當物件被點擊觸發事件,該回調函式(callback function)就會被調用(be called),也就是事件觸發後,回調函式會被放入回調列隊(Callback Queue)中排隊等待被執行,而當call stack為空時,正在排隊被執行的callback function就會被傳入call stack中,以便它被執行,而這樣的協調機制就稱為事件循環(Event Loop)。

我們其實可以將事件循環(Event Loop)白話一點理解為是交通管理員,因為Event Loop做為瀏覽器內的一個協調機制,安排了整個將回調函式從回調列隊安排放入call stack,讓它們可以被執行的過程,而這也是JavaScript非阻塞併發模型的如何被執行核心。

JavaScript的運行環境-Node.js(在瀏覽器外out of browser)

JavaScript,JavaScript_Engine,JavaScript_Runtime,JUST_IN_TIME_Compilation,jonas_schmedtmann
(JavaScript Runtime in Node.js from:Jonas Schmedtmann )

雖然說JavaScript要運行,必須透過瀏覽器附載的JavaScript Engine,但其實還有一個叫做Node.js的後端語言,讓JavaScript可以在離開瀏覽器的情況下運作,而當然少了瀏覽器,JavaScript的運行環境當然就沒有了WEB APIs,取而代之的是C++綁定和線程池(thread pool)

以上就是JavaScript運行環境的基本介紹,接下來讓我們更深入了解,JavaScript的執行環境及執行邏輯。


留言

這個網誌中的熱門文章

【南投│白毫禪寺】日式禪風的建築外型,卻是一部妙法蓮華經的具體呈現

【緬甸】緬甸自助旅行,一個人旅行不負責任懶人包:行程、住宿、換匯、簽證、交通、安全小提醒