V8引擎詳解(二)——AST

前言

本文是V8引擎詳解系列的第二篇,重點內容是關於V8在編譯運行過程當中生成抽象語法樹(AST)的一些理解,若是不知道v8基本運行過程的同窗能夠先看一下本人的第一篇文章 V8引擎詳解(一)——概述javascript

AST的概念

1、什麼是AST

看過我上一篇文章的同窗應該瞭解到V8引擎會先將javascript代碼轉換成AST(抽象語法樹),而事實上不管使用什麼編程語言(不管是解釋形語言仍是編譯形語言)都會將源代碼解析成 抽象語法樹(abstract syntax tree, AST),AST是計算機科學中很早的一個概念,不是V8特有的(只是V8在轉換過程當中作了很是多的優化)更不是javascript特有的。css

你們都知道想要運行javascript程序,須要處理原始的js文件才能讓v8引擎理解,而第一步就是將js代碼解析成一個抽象語法樹(AST),本質上就是一組表示程序結構的對象。而後再由Lgnition(將來個人文章中會有詳細介紹)編譯AST生成字節碼。html

2、AST的用途

AST的做用也不只僅是用來在v8的編譯上,咱們在實際的開發過程當中也是常用的,好比咱們經常使用的babel插件將 es6->es5 、ts->js 、代碼壓縮、css預處理器、eslint等等,他們在咱們的實際開發中都是必不可少的,而他們的底層原理其實也都是AST。java

3、結構

咱們已經知道了AST的一些基本概念也知道了一些用途,那麼到底AST是一個什麼樣結構的存在。我經過一個小工具給你們展現一下。(v8生成的AST結構和下面展現的是不太同樣的,V8生成的不太容易閱讀而且作了專門的優化,該圖是爲了方便你們先理解)jquery

工具的地址:esprima.org/demo/parse.…git

實際上,上圖的解析格式纔是符合業界規範的,最先由js之父Brendan Eich設計了第一代js引擎SpiderMonkey,後交給了Mozilla維護,再後來就開源了也就是 Parser_API 伴隨着ECMAScript的升級迭代也逐漸升級,慢慢的變成了如今的業界規範。es6

這個格式並不複雜你們能夠打開Parser_API配合esprima在線工具邊看邊調很快就能夠了解並掌握github

AST編譯過程

1、基本流程

咱們先來看一下流程圖:編程

你們能夠看到標準的AST工具和V8生成的AST的流程是稍微全部不一樣的,緣由是需求不一樣,好比咱們經常使用babel工具將es6->es5他須要保留原有的結構並進行全量的解析而後生成新的代碼,而v8生成的AST並不須要將es6進行轉換因此在生成AST過程當中會有很是多的優化操做的空間,這形成了流程上的差別以及形成咱們上文提到的V8生成的樹並非 Parser_API規範的樹,可是還有兩個最重要的步驟 scannerParser倒是都須要進行的。

2、詞法分析scanner

scanner最重要的功能就是將js源碼轉換成一個個有意義的詞(token)造成的數組。
咱們一塊兒經過一個簡單的小例子來了解一下它大概的運行流程仍是以一個最簡單語句進行分析。數組

var a = 123;
複製代碼

咱們設想代碼開始從 HTTP 協議收到的上面的字符流讀取字符,當咱們讀取到了第一個字符 "v":

  • 咱們會經過條件判斷語句判斷這個字符是 字母, "/" , "數字" , 空格 , "(" , ")" , ";" 等等。

  • 若是是 ';' 這種會生成{"type" : "Punctuator" , "value" : ";" }$放入數組中。

  • 若是是字母會繼續往下看如過仍是字母或者數字,會繼續這一過程直到不是爲止,這個時候發現找到這個字符串是一個 "var" 是一個Keyword而且下面一個字符是一個 "空格" 就會生成{ "type" : "Keyword" , "value" : "var" }放入數組中。

  • 它繼續向下找發現了一個字母 'a'(由於找到的上一個值是 "var" 這個時候若是它發現下一個字符不是字母可能直接就會報錯返回)而且後面是空格,生成{ "type" : "Identifier" , "value" : "a" }放到數組中。

  • 發現了一個 "=", 生成了{ "type" : "Punctuator" , "value" : "=" }放到了數組中。

  • 發現了'123',生成了{ "type" : "Numeric" , "value" : "123" }放到了數組中。

  • 最後發現了 ';', 生成了{ "type" : "Punctuator" , "value" : ";" }放入數組中。

咱們來看一下生成的結果

大概的原理基本就是這樣,固然 v8 有它本身的實現策略確定不會像我寫的那麼簡單,不過基本原理都是相似的。
若是有興趣想更深一步的瞭解 v8 scanner的實現策略能夠看一下v8官方出的文檔: v8.dev/blog/scanne…

事實上絕大多數語言的詞法部分都是用相似的狀態機原理實現的,甚至包括了咱們熟知的HTML官方文檔

3、語法分析parser

接下來就是 Parser 所作的工做實際上就是,對詞法分析以後的結果( 返回的數組)再次進行分析,分析過程當中將該數組按照特定的格式轉換成一個對象,基本的原理也是遍歷咱們的數組進行判斷來生成結構(AST樹),這裏我重點想講的是v8和普通工具生成的AST的一些差別。

以前說過普通的AST樹是使用同一個業界標準的,也就是說經過Parser生成的AST樹甚至是能夠直接給bable直接進行編譯轉換的,而經過v8生成的則是給v8本身的Lgnition引擎生成字節碼的生成的結果是優化過的,因此二者有着很大的差別,接下來我仍是經過幾個小例子來幫助你們理解。

仍是先從最簡單的開始

var a = 123;
複製代碼

對比一下生成的結果

若是你們有認真看過上文而且進行了簡單的實驗,左邊圖標準的結構你們確定能夠理解,而右邊圖 v8 生成AST的經過自身的描述大概意思也能和代碼結合起來,咱們接着往下看這段代碼:

var a = 6 * 7;
複製代碼

生成的結果:

標準的結構下面描述了 6 * 7 的這個結構,而 v8 直接將計算出來的結果生成了出來,這樣會減小 v8 後面經過Lgnition生成字節碼所消耗的資源,從而進行性能的提高,若是你以爲光節省的這點消耗不算什麼,那麼咱們繼續往下看:

function test () {
	return 1 + 2;
}
複製代碼

V8 發現了源文件只是聲明瞭一個叫 test 的函數可是並無調用時,根本沒有解析裏面的任何內容,那咱們接下來繼續看調用了會怎麼樣:

function test () {
	return 1 + 2;
}
var a = test();
複製代碼

咱們能夠發現 v8 生成的AST樹單獨的爲 test 函數生成了一個AST節點而再也不generating主流程中,這樣作有很是多的好處(好比在初始化的過程當中,由於不是本文的重點就很少贅述),更顯而易見的是隨着js源碼的增長v8生成的AST樹的大小和標準結構的AST樹大小的差距正在越拉越大,那麼解析起來天然有更多優點。

上面幾個簡單的小例子只是用最簡單易懂的代碼來解釋一下爲何 v8 生成的AST樹並非標準的規範的樹(對沒錯、就是爲速度而生)。
而實際上 v8 在生成AST樹上所作的工做和優化的內容也遠遠不止我上面提過的這幾個小例子,若是有興趣想更深一步的瞭解 v8 Parser的實現策略能夠看一下v8官方出的文檔: v8.dev/blog/prepar…

總結

本文主要介紹了 v8引擎 將進行編譯執行的第一步——生成AST,經過了解什麼是AST,以及生成AST的流程包括解釋了爲何 v8引擎 爲何沒有遵循AST的規範,做者並無經過源碼層面來分析生成的過程,主要是想經過讓更多人都能理解的方式來學習 v8引擎(主要是做者水平不夠),若是有什麼錯誤,請在評論中和做者一塊兒討論,若是您以爲本文對您有幫助請幫忙點個贊,感激涕零。

參考文章

v8.dev/blog/scanne…
v8.dev/blog/prepar…
cloud.tencent.com/developer/a…

相關文章

V8引擎詳解(一)——概述
V8引擎詳解(二)——AST
V8引擎詳解(三)——從字節碼看V8的演變

相關文章
相關標籤/搜索