掌握了AST,不再怕被問babel,vue編譯,Prettier等原理

概要

本文將經過如下幾個方面對AST進行學習javascript

  1. 爲何要了解AST,簡要說明AST在開發中的重要性
  2. 什麼是AST,對AST有一個直觀的認識
  3. AST是如何生成的,分析將代碼解析成AST的原理
  4. AST的具體應用,經過解讀babel原理、vue模板編譯過程,Prettier實現原理,來分析AST在開發中的具體使用。
  5. AST還能作什麼,結合工做,思考AST能爲咱們作些什麼

爲何要學習AST

AST(抽象語法樹)在開發過程當中扮演一個很是重要的角色,可是咱們卻不多去直接接觸它。css

不管是代碼編譯(babel),打包(webpack),代碼壓縮,css預處理,代碼校驗(eslint),代碼美化(pretiier),Vue中對template的編譯,這些的實現都離不開AST。前端

瞭解學習AST,可以幫助咱們更好的對上面說的這些工具原理進行理解,同時,咱們能夠利用它去開發一些工具,來優化咱們的開發流程,提升開發效率。vue

什麼是AST

AST是對源代碼的抽象語法結構的樹狀表現形式。java

在不一樣的場景下,會有不一樣的解析器將源碼解析成抽象語法樹。webpack

下面直觀的看一下AST是什麼樣的es6

代碼web

let answer = 2 * 3;
複製代碼

對應的AST語法樹正則表達式

{
    "type": "Program",
    "body": [
        {
            "type": "VariableDeclaration",
            "declarations": [
                {
                    "type": "VariableDeclarator",
                    "id": {
                        "type": "Identifier",
                        "name": "answer"
                    },
                    "init": {
                        "type": "BinaryExpression",
                        "operator": "*",
                        "left": {
                            "type": "Literal",
                            "value": 2,
                            "raw": "2"
                        },
                        "right": {
                            "type": "Literal",
                            "value": 3,
                            "raw": "3"
                        }
                    }
                }
            ],
            "kind": "let"
        }
    ],
    "sourceType": "script"
}
複製代碼

那麼AST是如何生成的呢?express

AST是如何生成的

AST是經過JS Parser (解析器),將js源碼轉化爲抽象語法樹,主要分爲兩步

1. 分詞

將整個的代碼字符串,分割成語法單元數組(token)。 JS中的語法單元(token)指標識符(function,return),運算符,括號,數字,字符串等能解析的最小單元。主要有如下幾種:

  1. 標識符
    沒有被引號括起來的連續字符,能夠包含字母、數字、_、$,其中數字不能做爲開頭。
    標識符多是var,return,function等關鍵字,也多是true,false這樣的內置常量,或是一個變量。具體是哪一種語義,分詞階段不區分,只要正確拆分便可。

  2. 數字 十六進制,十進制,八進制以及科學表達式等都是最小單元

  3. 運算符: +、-、 *、/ 等

  4. 字符串 對計算機而言,字符串只會參與計算和展現,具體裏面細分不必分析

  5. 註釋 不論是行註釋仍是塊註釋,對於計算機來講並不關心其內容,因此能夠做爲不可再拆分的最小單元

  6. 空格 連續的空格,換行,縮進等,只要不在字符串中都沒有實際的邏輯意義,因此連續的空格能夠做爲一個語法單元。

  7. 其餘,大括號,中括號,小括號,冒號 等等。

依然拿上面的代碼做爲例子,分詞後生成的語法單元數組以下

[
    {
        "type": "Keyword",
        "value": "var",
        "range": [
            0,
            3
        ]
    },
    {
        "type": "Identifier",
        "value": "answer",
        "range": [
            4,
            10
        ]
    },
    {
        "type": "Punctuator",
        "value": "=",
        "range": [
            11,
            12
        ]
    },
    {
        "type": "Numeric",
        "value": "2",
        "range": [
            13,
            14
        ]
    },
    {
        "type": "Punctuator",
        "value": "*",
        "range": [
            15,
            16
        ]
    },
    {
        "type": "Numeric",
        "value": "3",
        "range": [
            17,
            18
        ]
    },
    {
        "type": "Punctuator",
        "value": ";",
        "range": [
            18,
            19
        ]
    }
]
複製代碼

2. 語義分析

語義分析的目的是將分詞獲得的語法單元進行一個總體的組合,分析肯定語法單元之間的關係。

簡單來講,語義分析能夠理解成對語句(statement)和表達式(expression)的識別。

  1. 語句,一個具有邊界的代碼區域。相鄰的兩個語句之間從語法上講互不影響。好比: var a = 1; if(xxx){xxx}
  2. 表達式,指最終會有一個結果的一小段代碼,它能夠嵌入到另外一個表達式中,且包含在表達式中。好比:a++i > 0 && i< 6

語義分析是一個遞歸的過程,它會將分詞分析出來的數組轉化成樹形的表達形式。同時,會驗證語法,語法若是存在錯誤的話,會拋出語法錯誤。

AST的具體應用

文章一開始就說到了,babel,webpack,css預處理,eslint等都應用到了AST樹,那麼AST到底作了一個什麼樣的角色呢!? 下面咱們就來看一下。

首先看一下babel工做原理的實現。

babel實現原理

babel是一個javascript編譯器,用來將es6語法編譯成es5

babel的工做能夠分爲3個階段:

第1步 解析(Parse)
經過解析器babylon將代碼解析成抽象語法樹

第2步 轉換(TransForm)
經過babel-traverse plugin對抽象語法樹進行深度優先遍歷,遇到須要轉換的,就直接在AST對象上對節點進行添加、更新及移除操做,好比遇到箭頭函數,就轉換成普通函數,最後獲得新的AST樹。

第3步 生成(Generate)
經過babel-generator將AST樹生成es5代碼

vue模板編譯過程

Vue 提供了 2 個版本,一個是 Runtime + Compiler ,另外一個是 Runtime only 的,前者是包含編譯代碼的,會把編譯的過程放在運行時作,後者是不包含編譯代碼的,須要藉助 webpack 的vue-loader把模板編譯render函數。無論使用哪一個版本,都有一個環節,就是將模板編譯成render函數。

下面咱們分析下vue模板的編譯過程,這也是vue源碼實現中很是重要的一個模塊。 vue模板的編譯過程分爲3個階段

第1步 解析(Parse)

const ast = parse(template.trim(), options)
複製代碼

將模板字符串解析生成 AST,這裏的解析器是vue本身實現的,解析過程當中會使用正則表達式對模板順序解析,當解析到開始標籤、閉合標籤、文本的時候都會有相對應的回調函數執行,來達到構造 AST 樹的目的。

生成的AST 元素節點總共有 3 種類型,1 爲普通元素, 2 爲表達式,3爲純文本。下面看一個例子

<ul :class="bindCls" class="list" v-if="isShow">
    <li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
</ul>
複製代碼

上面模板解析生成的AST樹以下:

ast = {
  'type': 1,
  'tag': 'ul',
  'attrsList': [],
  'attrsMap': {
    ':class': 'bindCls',
    'class': 'list',
    'v-if': 'isShow'
  },
  'if': 'isShow',
  'ifConditions': [{
    'exp': 'isShow',
    'block': // ul ast element
  }],
  'parent': undefined,
  'plain': false,
  'staticClass': 'list',
  'classBinding': 'bindCls',
  'children': [{
    'type': 1,
    'tag': 'li',
    'attrsList': [{
      'name': '@click',
      'value': 'clickItem(index)'
    }],
    'attrsMap': {
      '@click': 'clickItem(index)',
      'v-for': '(item,index) in data'
     },
    'parent': // ul ast element
    'plain': false,
    'events': {
      'click': {
        'value': 'clickItem(index)'
      }
    },
    'hasBindings': true,
    'for': 'data',
    'alias': 'item',
    'iterator1': 'index',
    'children': [
      'type': 2,
      'expression': '_s(item)+":"+_s(index)'
      'text': '{{item}}:{{index}}',
      'tokens': [
        {'@binding':'item'},
        ':',
        {'@binding':'index'}
      ]
    ]
  }]
}
複製代碼

第2步 優化語法樹(Optimize)

optimize(ast, options)
複製代碼

vue模板中並非全部數據都是響應式的,有不少數據是首次渲染後就永遠不會變化的,那麼這部分數據生成的 DOM 也不會變化,咱們能夠在patch的過程跳過對他們的比對。
此階段會深度遍歷生成的 AST樹,檢測它的每一顆子樹是否是靜態節點,若是是靜態節點則它們生成 DOM 永遠不須要改變,這對運行時對模板的更新起到極大的優化做用。

遍歷過程當中,會對整個 AST 樹中的每個 AST 元素節點標記static和staticRoot(遞歸該節點的全部children,一旦子節點有不是static的狀況,則爲false,不然爲true)。

通過該階段,上面例子中的ast會變成

ast = {
  'type': 1,
  'tag': 'ul',
  'attrsList': [],
  'attrsMap': {
    ':class': 'bindCls',
    'class': 'list',
    'v-if': 'isShow'
  },
  'if': 'isShow',
  'ifConditions': [{
    'exp': 'isShow',
    'block': // ul ast element
  }],
  'parent': undefined,
  'plain': false,
  'staticClass': 'list',
  'classBinding': 'bindCls',
  'static': false,
  'staticRoot': false,
  'children': [{
    'type': 1,
    'tag': 'li',
    'attrsList': [{
      'name': '@click',
      'value': 'clickItem(index)'
    }],
    'attrsMap': {
      '@click': 'clickItem(index)',
      'v-for': '(item,index) in data'
     },
    'parent': // ul ast element
    'plain': false,
    'events': {
      'click': {
        'value': 'clickItem(index)'
      }
    },
    'hasBindings': true,
    'for': 'data',
    'alias': 'item',
    'iterator1': 'index',
    'static': false,
    'staticRoot': false,
    'children': [
      'type': 2,
      'expression': '_s(item)+":"+_s(index)'
      'text': '{{item}}:{{index}}',
      'tokens': [
        {'@binding':'item'},
        ':',
        {'@binding':'index'}
      ],
      'static': false
    ]
  }]
}
複製代碼

第3步 生成代碼

const code = generate(ast, options)
複製代碼

經過generate方法,將ast生成render函數

with(this){
  return (isShow) ?
    _c('ul', {
        staticClass: "list",
        class: bindCls
      },
      _l((data), function(item, index) {
        return _c('li', {
          on: {
            "click": function($event) {
              clickItem(index)
            }
          }
        },
        [_v(_s(item) + ":" + _s(index))])
      })
    ) : _e()
}
複製代碼

Prettier實現原理

經過上面對babel實現原理和vue模板的編譯原理能夠看出,他們的實現有不少相同之處,都是先將源碼解析成AST樹,而後對AST樹就行處理,最後生成想要的東西。

Prettier的實現一樣是這樣,首先依然是將代碼解析生成AST樹,而後是對AST遍歷,調整長句,整理空格,括號等,最後輸出代碼,這裏就不贅述了。

小結

咱們分析了Babel原理、vue模板編譯過程、Prettier原理,這裏咱們簡單總結一下。
若是把源碼比做一個機器,那麼分詞過程就是將這臺機器拆分紅一個個零件,語義分析過程就是分析每一個零件的位置以及做用,而後根據須要對零件進行加工處理,最後再組裝成一個新的機器。

AST還能作什麼

那麼工做中咱們能使用AST作些什麼呢?!

這裏就要發揮想象了,看看咱們平常工做中有什麼需求是能夠經過AST開發個工具來解決。
好比,能夠經過AST能夠將代碼自動轉成流程圖;
或者根據自定義的註釋規範,經過工具自動生成文檔;
或是經過工具自動生成骨架屏文件。

你還有什麼好想法呢?

歡迎關注個人公衆號「前端小苑」,我會按期在上面更新原創文章。也能夠加我微信 yu_shihu_ 共同交流技術。

相關文章
相關標籤/搜索