對於js,AST能幹什麼?javascript
ps: 本文只探討AST的概念以及使用,編譯原理的其餘知識不作太多描述vue
@babel/core
java
@babel/types
node
毫無疑問js是一個解釋型語言,有疑問能夠參考這篇文章
因此這裏只簡單描述一下babel的編譯過程(大霧),有興趣瞭解編譯型語言詳細編譯過程的能夠看這本 《編譯原理》es6
和編譯器相似,babel 的轉譯過程也分爲三個階段,這三步具體是:解析 Parse
將代碼解析生成抽象語法樹( 即AST ),也就是計算機理解咱們代碼的方式(擴展:通常來講每一個 js 引擎都有本身的 AST,好比熟知的 v8,chrome 瀏覽器會把 js 源碼轉換爲抽象語法樹,再進一步轉換爲字節碼或機器代碼),而 babel 則是經過babylon
實現的 。簡單來講就是一個對於 JS 代碼的一個編譯過程,進行了詞法分析與語法分析的過程。chrome轉換 Transform
對於 AST 進行變換一系列的操做,babel 接受獲得 AST 並經過babel-traverse
對其進行遍歷,在此過程當中進行添加、更新及移除等操做。typescript生成 Generate
將變換後的 AST 再轉換爲 JS 代碼, 使用到的模塊是babel-generator
。小程序而
babel-core
模塊則是將三者結合使得對外提供的API作了一個簡化。segmentfault
demo.js
是我隨便copy來的一段代碼瀏覽器
isLeapYear() function isLeapYear(year) { const cond1 = year % 4 == 0; //條件1:年份必需要能被4整除 const cond2 = year % 100 != 0; //條件2:年份不能是整百數 const cond3 = year % 400 ==0; //條件3:年份是400的倍數 const cond = cond1 && cond2 || cond3; console.log(cond) if(cond) { alert(year + "是閏年"); return true; } else { alert(year + "不是閏年"); return false; } }
如今我要把它轉成AST,這裏使用@babel/core
來解析,它提供了一個parse
方法來將代碼轉化爲AST。
parse.ts
就是個人解析工具
import * as fs from 'fs' import * as path from 'path' import { parse} from '@babel/core' const js_path = path.resolve(__dirname, '../demo.js') let code = fs.readFileSync(js_path, { encoding: 'utf-8' }) const js_ast = parse(code) console.log(js_ast)
能夠看到AST結果以下:
結果太長就不一一解析了,只說type
屬性,就表示了這一行代碼作了什麼,VariableDeclaration
就表示這是一句聲明語句, CallExpression
則表明這是一個調用函數的語句
@babel/core
提供了一個transform
方法,輸入代碼和修改代碼的規則,輸出修改過的AST,它看起來是這樣的:
const ArrowPlugins = { visitor: { VariableDeclaration(path: NodePath) { // ... }, CallExpression(path: NodePath) { // ... } } } const d = transform(code, { plugins: [ ArrowPlugins ] })
當命中對應的type時就會走進相應的回調函數,接下來寫個小🌰,將alert
,console.log
以及所有註釋
都刪除,而後將 ==
和!=
改爲 ===
和!==
import * as fs from 'fs' import * as path from 'path' import { transform, parse, NodePath } from '@babel/core' import { VariableDeclaration, CallExpression, MemberExpression, Identifier, BinaryExpression } from '@babel/types' const js_path = path.resolve(__dirname, '../demo.js') let code = fs.readFileSync(js_path, { encoding: 'utf-8' }) // const js_ast = parse(code) // debugger const ArrowPlugins = { visitor: { VariableDeclaration(path: NodePath) { // 修改== -> === const node = path.node as VariableDeclaration node.declarations.map((item) => { const init = item.init as BinaryExpression const equalMap = { '==': '===', '!=': '!==' } init.operator = equalMap[init.operator] || init.operator }) // 刪除註釋 delete node.leadingComments delete node.trailingComments }, CallExpression(path: NodePath) { // 調用函數 const node = path.node as CallExpression // 刪除console.xxx 和 alert const memberExpressionCallee = node.callee as MemberExpression const identifierCallee = node.callee as Identifier const object = memberExpressionCallee.object as Identifier if (object && object.name === 'console' || identifierCallee.name === 'alert') { path.remove() } // 刪除註釋 delete node.leadingComments delete node.trailingComments } } } const d = transform(code, { plugins: [ ArrowPlugins ] }) console.log(d.code)
只是簡單地使用了一下@babel
提供的方法將代碼轉成AST,並在樹枝上作一些簡單的修修改改,最後轉成目標代碼,若是隻是平常使用或者用來本身寫babel插件通常是足夠了,想要了解更多的編譯原理知識須要更系統的學習。
等我看完《編譯原理》(大霧)
,再繼續更新系列文章