Babel的初步認識

Babel是前端很常用的轉碼器,更準確地說是轉譯器,是從源碼到源碼的轉換編譯器,例如可以將我們按照ES6標準寫的代碼轉為ES5標準,也就是說可以直接使用ES6的最新標準來編寫腳本,而不用擔心現有環境是否支持此標準。

例如:Babel可以將我們最常用的箭頭函數:

const demo = item => item + 1;

轉譯成ES5的函數寫法:

const demo = function(item){
    return item + 1;
}

將ES6標準轉譯成ES5,不用擔心各大瀏覽器是否已經支持ES6的最新標準這個確實是解決了我們工作中一大問題,但是Babel的功能并不止于此,它是可以轉譯很多種語法的,例如我們最常用的react中JSX的語法,而且Babel的核心就是利用插件,通過不同的插件可以轉譯不同的語法,讓我們可以暢快的去嘗試最新的語法。

Babel的三大主要步驟

我們先來看一張圖,這是我網上找來的,我感覺這張圖已經把Babel的工作步驟畫的很清楚了。


Babel流程圖

從上圖可以看出Babel的三大步驟分別是:解析(parse),轉換(transform),生成(generate)。嘿嘿,我發現圖中的英文有點問題,大家可以查一下是不是,如果是我翻譯錯了請指正。

什么是AST?
在詳細解釋這三大步驟前,我們有必要先來了解一下什么是AST?
“ AST ”其實叫做“ 抽象語法樹 ”,是源代碼的抽象語法結構的樹狀表現形式,其實個人覺得babel對于AST和我們熟悉的jquery對于DOM有點像。我們可以想象一下如何將JS代碼用樹狀表示出來。

var a = 1 + 1
var b = 2 + 2

上面聲明了兩個變量,如何用樹狀表示他們呢?首先一定會有東西可以代表這些聲明、變量名、常量等等的信息。很明顯,這棵樹上有兩個變量,兩個變量名a和b,有兩個運算語句,操作符都是+號。但是有了這些還不夠,既然是樹,樹枝連樹枝,還必須建立起彼此之間的關系,比如一個聲明語句,聲明類型是var,左側是變量名,右側是表達式,有了這些信息我們就可以還原這個程序了。這個就是把源碼解析成AST時所做的事情了。

在AST中我們用node(節點)來表示每個代碼片段,比如上面程序的整體就是一個節點(Program,所有的AST根節點都是Program節點),然后下面有兩條語句,所以它的body屬性上就兩個聲明節點VariableDeclaration。所以上面程序的AST類似這樣:

AST結構圖

從圖上可以看出節點上用了各個屬性來表示各種信息以及程序之間的關系。

解析(parse):

在大概了解了AST是個啥東西后,我們可以來了解三大步驟了,首先是第一步解析。主要是為了接收代碼并輸出AST,也就是將代碼變為樹狀,這個步驟又分兩個階段:詞法分析(Lexical Analysis)和 語法分析(Syntactic Analysis)。

詞法分析
詞法分析階段是把字符串形式的代碼轉換成令牌(tokens)流。你可以把tokens看成是一個語法片段數組。例如:n*n代碼經過詞法分析階段后轉換成了tokens:

// n*n
[
  { type: { ... }, value: "n", start: 0, end: 1, loc: { ... } },
  { type: { ... }, value: "*", start: 2, end: 3, loc: { ... } },
  { type: { ... }, value: "n", start: 4, end: 5, loc: { ... } },
  ...
]

每一個type又有一組屬性來描述該令牌:

{
  type: {
    label: 'name',
    keyword: undefined,
    beforeExpr: false,
    startsExpr: true,
    rightAssociative: false,
    isLoop: false,
    isAssign: false,
    prefix: false,
    postfix: false,
    binop: null,
    updateContext: null
  },
  ...
}

語法分析
語法分析階段是把一個令牌(tokens)流轉換成AST的形式,以便于后續的操作。

轉換(transform)

第二步是轉換,主要是用來接收解析好的AST并對其進行遍歷,在此過程中對節點進行添加、更新或者移除等操作,準確的說是利用我們配置好的plugins/presets把Parser生成的AST轉變為新的AST,這一步是插件要介入工作的部分,從三大步驟的圖中也可以看出占了很大一塊的比重,足以看出這個轉換過程就是Babel中最復雜的部分,我們平時配置的plugins/presets就是作用在這里了。

生成(generate)

第三步是生成,最后一步把最后經過一系列轉換后的最新AST轉換成字符串形式的代碼,同時還會創建源碼映射(source maps)。代碼生成反而比較簡單,只要深度優先遍歷整個AST,然后構建可以表示轉換后代碼的字符串就可以了。

Visitors(訪問者)

當我們說到“進入”一個節點時,其實是在說我們在訪問它,之所以使用這樣的術語是因為有一個訪問者模式的概念。
這里的訪問者是一個用于AST遍歷的跨語言的模式。簡單來說就是一個對象,定義了用于在一個樹狀結構中獲取具體節點的方法,例如:

const MyVisitor = {
  Identifier: {
    // 當進入Identifier節點的時候執行
    enter() { 
      console.log("Entered");
    },
    // 當退出Identifier節點的時候執行
    exit() {
      console.log("Exited!");
    }

  }
};

每一個節點都會有自己對應的type,比如變量節點Identifier等。上例中我們給babel提供了一個MyVisitor對象,在這個對象上面我們以這些節點的type做為key,已一個函數作為值,這樣在遍歷進入到對應節點時,babel就會執行對應的enter函數,向上遍歷退出對應節點時,babel就會去執行對應的exit函數。

Paths(路徑)
我們通過visitor可以在遍歷到對應節點執行對應的函數,可是要修改對應節點的信息,還是不夠,畢竟要增刪節點,我們不能等進入節點了才執行,我們還需要拿到對應節點的信息以及節點和所在的位置(即和其他節點間的關系), visitor在遍歷到對應節點執行對應函數時候會給我們傳入path參數,輔助我們完成上面這些操作。Path 是表示兩個節點之間連接的對象,而不是當前節點,我們上面訪問到了Identifier節點,它傳入的 path參數看起來是這樣的:

{
  "parent": {
    "type": "VariableDeclarator",
    "id": {
      ...
    },
    ....
  },
  "node": {
    "type": "Identifier",
    "name": "..."
  }

從上例可以看出:path.node.name可以獲得當前節點的name,path.parent.id可以獲得父節點的id,另外path對象上面還包含了添加、更新、移動和刪除節點有關的很多方法,至于這些有關的方法就不再這里展開了,可以看文檔解決。上面說visitor在遍歷到對應節點執行對應函數時候會給我們傳入path參數,所以我們可以根據這個修改一下上文中的MyVisitor函數:

const MyVisitor = {
  Identifier: {
    // 當進入Identifier節點的時候執行
    enter(path) { 
      console.log('traverse enter a Identifier node the name is ' + path.node.name);
    },
    // 當退出Identifier節點的時候執行
    exit(path) {
      console.log('traverse exit a Identifier node the name is ' + path.node.name);
    }
  }
};

這樣我們就可以操作想要改變的節點了,嗯嗯~~ very good!!

總結

最后我們總結一下,Babel最重要的就是熟悉它的工作步驟,也就是它的原理:

  1. 接收源代碼
  2. 將源代碼轉成字符串形式
  3. 把字符串形式的源代碼轉換成令牌流
  4. 把一個令牌流轉換成AST的樹狀形式。
  5. 接收AST并對其進行遍歷,在此過程中可以對節點進行各 種操作,比如添加、更新、移除等等。
  6. 深度遍歷最終的AST樹,然后構建可以表示轉換后代碼的字符串,并且同時創建源代碼映射。

其實很多猿兄都和我一樣,剛接觸babel的時候,直接上手用,看著文檔知道如何用,但是不知背后的原理,今天這一片筆記也是看了好幾篇文檔和大牛的博客整理出來的比較關鍵的幾點,看上去簡簡單單的轉譯器,其實背后的實現還是挺不容易的,我們已經簡單的分析了代碼,并且可以修改一些抽象語法樹上的內容來達到我們的目的,不過開頭的時候也說了對于Babel而言插件是很重要的,現階段Babel已經不僅僅是去轉換ES6了,最常用的還有轉換react中JSX的語法,所以除了懂得原理以外,我們也可以自己實際去編寫一些有意思的插件來應用與自己的工作中,更好的提高對Babel的理解,今天介紹就到這里,還是那句話如有總結不到位的,希望各位猿兄指教。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容