Stories

Detail Return Return

AST初探 - Stories Detail

前端開發中,使用了很多工具,譬如webpack、eslint來提升研發效率,但我們並不知道這些工具的實現原理。基於這些工具的核心都是抽象語法樹,那我們就從抽象語法樹開始理解底層原理的新世界吧。

一、抽象語法樹是什麼

顧名思義,首先可以確定的是,這是一顆跟語法相關的樹。

先上一盤硬菜,維基百科定義如下:

In computer science, an abstract syntax tree (AST), or just syntax tree, is a tree representation of the abstract syntactic structure of source code written in a programming language.

也就是説,抽象語法樹,是通過編程語言編寫的代碼的抽象語法結構。

用通俗的話講,我們所使用的編程語言是一門對人類友好的語言,但是對於程序分析來講,並不友好。因此,需要將編程語言轉譯成對程序分析友好的語言。

太乾了,來點配菜。

我們結合編譯過程,來説明抽象語法樹的作用。

如下圖所示,一般的編譯過程分為六個過程。

圖片1.png

那麼在語法分析階段,就是要將詞法分析階段得到的分詞結果整合成一棵語法樹。簡單舉個例子:

代碼:

function foo(a) {
    let b = a + 3;
    return b;
}

將這個函數轉成抽象語法樹,其核心部分如下圖所示:

blockstatement 塊級作用域。

VariableDecalaration 變量聲明

VariableDecalarator 變量聲明器

Returnstatement 返回語句

圖片2.png

在轉成語法樹之後,就可以對語句進行語法檢查或進行修改了。

二、 語法樹的應用

前面提到,可以通過對語法樹分析來進行語法檢查,有沒有很熟悉? 有沒有想到我們日常寫代碼過程中用到的eslint 插件、括號高亮插件等。 沒有錯,他們就是通過對ast 抽象語法樹進行分析,來達成語法檢查和高亮目的。

話不多説,看圖。

圖片3.png

這是一個ast 在我們開發流程中的一個應用總結。

編寫代碼:我們用 vue的 模板語法去定義一些功能,而這些模板語法最後都要通過對 ast 進行分析最終轉為原生 html 和原生 js。

babel 、webpack 和 ast 的關係:

開發過程中,我們使用了大量處於提案階段的 es6 的 語法,譬如裝飾器、箭頭函數、模板字符串等,而這些語法,實際上瀏覽器本身並不支持。因此,需要將其轉成 瀏覽器支持的 es5 代碼。這個es 降級的過程就是通過 babel 完成的,而 babel 的核心也就是通過對 ast 進行更改,從而實現 es 語法降級。

webpack 作為模塊化開發的主力軍,在打包階段,也是通過對語法樹進行分析,最終打包成一個文件上傳到服務器上去的。

瀏覽器執行代碼和 ast 的關係:

如圖中所示,瀏覽器執行js 代碼的過程和我們上面所闡述的編譯的六個階段是極其一致的。

綜上所述,語法樹和我們開發的整個過程都是息息相關的。

認識到了語法樹在開發過程中的重要性。 接下來,我們通過 babel 的核心庫 來簡單模擬 es6 轉 es5 的過程。

轉譯過程大致可以分為三步:

  1. 生成語法樹
  2. 語法樹遍歷,更改
  3. 再次遍歷語法樹,生成新的代碼。

通過庫 esprima 生成語法樹, 通過庫 estraverse 遍歷語法樹並進行語法更改, 通過庫 escodegen 遍歷語法樹,並生成新的代碼。

三、 語法樹的生成

語法樹生成使用的庫是 esprima。

我們先通過一個簡單的es6 例子來説明。

es6 通過 let 關鍵字進行變量聲明,從而實現塊級作用域。那麼轉成 es 5 的話只能是 ‘var’。即,代碼 ”let a = 15; “ 轉變為 ”var a = 15;“。

首先,導入所使用的幾個庫(庫的地址在本文末尾。)並定義字符串。

const prima = require('esprima');
const codegen = require('escodegen');
const traverse = require('estraverse');
var  code2 = 'let a = 2';

我們知道,如果要轉成語法樹,首先要進行詞法分析,通過調用esprima 下的 tokenize函數(console.log(prima.tokenize(code2)))可以查看其生成的token。也可以通過可視化工具esprima 可視化查看詞法分析結果。

圖片4.png

從圖中可以看出,esprima 對字符串中的每一個此進行解析,並賦予相應的 type。

在詞法解析結束後,再進行語法解析形成 ast 樹,可以通過命令行

console.log(prima.parseScript(code2));

查看其 ast 的結果,也可以通過 ast 可視化工具 查看其結果。這裏展示通過 ast 可視化工具的結果:

es6 語法樹:

圖片5.png

es5語法樹:
圖片6.png

對比這兩棵語法樹,我們可以看出,其唯一的區別在於,變量聲明下,其 kind 節點的值。 那麼,接下來,在遍歷語法樹的時候,我們只要對 node 下的 kind 節點進行變更就可以完成語法轉變了。

四、語法樹的遍歷和更改

我們可以通過 estraverse下的 traverse 函數進行語法樹遍歷並進行相應更改。

const prima = require('esprima');
const codegen = require('escodegen');
const traverse = require('estraverse');
var  code2 = 'let a = 2';

五、生成 es5 代碼

我們通過 escodegen 下的 generate 函數生成 新的es5 代碼:

let code2_es5 = codegen.generate(ast2);
console.log(code2_es5);
//'var a = 2;'

寫到這裏,基本上對 ast 的 what、why、how 進行了一個基本的介紹。 但美中不足的是,上面對於語法樹的解析只是利用庫函數進行的。接下來,給大家介紹一些 ast 相關的小知識,並嘗試帶大家,讀一讀這些函數的源代碼。

六、AST 小知識

js 解析器有哪些?我們所使用的工具內核是什麼?

常用的 js 解析器有:

esprima: https://github.com/jquery/esp...

acorn:https://github.com/acornjs/acorn

acorn 的誕生晚於 esprima,期因是 esprima 轉換速度太慢。

而 babel 目前所用的解析器 fork 自 acorn。webpack 的核心 parser 也是 acorn。而 eslint 作為一個可配置的代碼規範檢查工具,可以任意選擇定義解析器來使用。

而不管是那個解析器,他們解析得到的 ast 樹都符合ast 規則:https://github.com/estree/estree

規則起源:

在v8引擎之前,最早js引擎是SpiderMonkey,第一個版本由js作者Brendan Eich設計,後交給Mozilla組織維護。js引擎在執行js文件時,都會先將js代碼轉換成抽象語法樹(AST)。有一天,一位Mozilla工程師在FireFox中公開了這個將代碼轉成AST的解析器Api,也就是Parser_API,後來被人整理到github項目estree,慢慢的成了業界的規範。

七、 相關推薦

從零實現一個簡易編譯器:https://github.com/YongzeYao/...

Babel 插件和 ast 轉化過程:https://juejin.cn/post/684490...

Babel 介紹:https://juejin.cn/post/684490...

Add a new Comments

Some HTML is okay.