【1】解析與編譯

解析與編譯

Javascript 從源程序到可以被計算機識別的目標程序主要包含兩個階段:

  • 解析生成抽象語法樹
  • 編譯執行

解析

以V8引擎爲例,前置的解析被分爲兩種類型:Pre-ParserFull-Parser

Pre-Parser,主要負責對整個 Javascript 源代碼進行必要的前期檢查,判斷是否存在語法錯誤。只在 Top-level 代碼執行前進行。

這裏是一種比較普遍的流程,在使用某個事物之前,靠譜的做法當然是先明確下能不能用。

Full-Parser,做的工作相應比較多些,包含:

  • 通過分詞/詞法分析解析/語法分析生成抽象語法樹(Abstract Syntax Tree,AST)
  • 進行作用域分析,爲變量分配內存,生成可用的上下文作用域。具體包括:

    • 將形參作爲 GO/AO 的屬性,賦值爲實參值
    • 將變量作爲 GO/AO 的屬性,賦值爲 undefind
    • 將函數作爲 GO/AO 的屬性,賦值爲其函數體
    • 創建該函數的作用域鏈等
GO (Global Object),全局環境下創建全局對象。
AO (Active Object),函數執行前創建激活對象。

Full-Parser,在Top-level代碼和非Top-level代碼執行前都會進行。函數在被調用執行前,經過Full-Parser生成抽象語法樹提供給JIT編譯器,生成目標語言執行。

Top-level 是指源代碼初次加載時需要被首先運行到的“頂層”代碼。

V8引擎不一次性完成 Javascript 源代碼對應的 AST 信息,而是在知道要執行哪段代碼前,將這段代碼完成 AST 的生成。

想要了解 AST 信息,可查看 AST 生成工具

問題:
【1】變量提升的原因是因爲爲了提高執行效率,在代碼執行前Full-Parser階段爲變量分配資源。
【2】函數聲明優先於變量聲明是因爲變量聲明只檢查變量是否存在,而函數聲明需要更新變量值。

編譯執行:

在瞭解JS的編譯過程前,先明確兩個概念:解釋器、編譯器。
解釋器就像口譯員,從源代碼第一行開始進行解析編譯執行。
編譯器則是直接將完整的源代碼完全編譯生成目標程序,從而快速執行。
解釋器與編譯器各有各的優勢,解釋器能夠快速啓動與執行,瀏覽器能夠快速執行JS代碼對Web頁面來說是非常重要的,這也是爲什麼瀏覽器使用解釋器來解析JS源代碼。
但是,在使用解釋器也存在着一些弊端,比如在處理循環的時候,解釋器並沒有很好的處理重複的“翻譯”工作。所以在早期(2008年以前)JS執行的速度並不是很快。
然而,編譯器除了編譯時間長一些,可以對代碼有更好的優化,從而能夠更快的執行代碼。因而,在2008年,多種瀏覽器添加了即時編譯器(JIT, just in time),使得JS的執行速度提高了10倍。
那麼JIT做了些什麼事情呢?
JIT包含兩部分構成:

  • 基線編譯器
  • 優化編譯器

首先源代碼會經過基線編譯器解析編譯生成未優化的目標代碼。同時JS引擎有稱爲監視器/分析器的部件,記錄代碼執行的次數和方式。
當某段代碼執行次數變多時,如函數頻繁調用、循環代碼塊等,基線編譯器會對這段代碼做一些優化。當這段代碼執行次數越來越多,監視器會將這段代碼交給優化編譯器,從而生成更快的版本。
爲了生成更快的版本,優化編譯器必須做一些假設,並且生成的代碼也是默認這些假設都成立的。但是,如果在代碼執行過程中,某個假設失敗了,瀏覽器將執行返回到解釋器或者基線編譯的版本。
這個過程稱爲去優化,所以循環中數據類型與結構的變化可能會對優化編譯過程造成影響。

具體流程如下:
V8 Compiler

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章