從一個誤寫的逗號談開去——JS代碼是如何被壓縮的

故事起源於一個很小問題,我寫了個代碼,被質疑有問題:簡化以後大概以下:javascript

let a;
const x = { b: 123 };
a = 123,
delete x

被質疑的主要緣由是第三行a=123的後面爲何是逗號,不是分號。坦白來講,我是簡單的手誤,將分號錯寫成了逗號。可是感受貌似應該也沒有什麼問題,畢竟uglifyjs會將某些語句進行合併,將分號變成逗號。繼而再一想,uglifyjs是如何來進行代碼壓縮的、它是如何知道該合併哪些語句,不合並哪些語句的、 它又有哪些合併規則?因而有了本文。html

1. AST(抽象語法樹)

要想了解JS的壓縮原理,須要首先了解AST。java

抽象語法樹:AST(Abstract Syntax Tree),是源代碼的抽象語法結構的樹狀表現形式,這裏特指編程語言的源代碼。樹上的每一個節點都表示源代碼中的一種結構。之因此說語法是「抽象」的,是由於這裏的語法並不會表示出真實語法中出現的每一個細節。

舉個例子:es6

image.png

image.png

從上面兩個例子中,能夠看出AST是源代碼根據其語法結構,省略一些細節(好比:括號沒有生成節點),抽象成樹形表達。抽象語法樹在計算機科學中有不少應用,好比編譯器、IDE、壓縮代碼、格式化代碼等。[1]npm

2. 代碼壓縮原理

瞭解了AST以後,咱們再分析一下JS的代碼壓縮原理。簡單的說,就是編程

1. 將code轉換成AST
2. 將AST進行優化,生成一個更小的AST
3. 將新生成的AST再轉化成code

PS:具體的AST樹你們能夠在astexplorer上在線得到api

babel,eslint,v8的邏輯均與此相似,下圖是咱們引用了babel的轉化示意圖:
1.jpgbabel

以咱們以前被質疑的代碼爲例,看看它在uglify中是怎麼樣一步一步被壓縮的:app

// uglify-js的版本須要爲2.x, 3.0以後uglifyjs再也不暴露Compressor api
// 2.x的uglify不能自動解析es6,因此這裏先切換成es5
// npm install uglify-js@2.x
var UglifyJS = require('uglify-js');

// 原始代碼
var code = `var a;
var x = { b: 123 };
a = 123,
delete x`;

// 經過 UglifyJS 把代碼解析爲 AST
var ast = UglifyJS.parse(code);
ast.figure_out_scope();

// 轉化爲一顆更小的 AST 樹
compressor = UglifyJS.Compressor();
ast = ast.transform(compressor);

// 再把 AST 轉化爲代碼
code = ast.print_to_string();

// var a,x={b:123};a=123,delete x;
console.log("code", code);

到這裏,咱們已經瞭解了uglifyjs的代碼壓縮原理,可是尚未解決一個問題——爲何某些語句間的分號會被轉換爲逗號,某些不會轉換。這就涉及到了uglifyjs的壓縮規則。frontend

3. 代碼壓縮規則

因爲uglifyjs的代碼壓縮規則不少,咱們這裏只分析與本文中相關的部分:

uglifyjs的所有壓縮規則能夠參見:《[解讀uglifyJS(四)——Javascript代碼壓縮](https://rapheal.sinaapp.com/2014/05/22/uglifyjs-squeeze/#more-705)》
連續的"表達式語句"能夠合併成一個逗號表達式

image.png

PS:在線demo

這其中須要注意的是隻有「表達式語句」才能被合併,那麼什麼是表達式語句呢?

表達式 VS 語句 VS 表達式語句

表達式:表達式都會返回一個值,能夠放在任何一個須要值的地方

例如:

a; //返回a的值
    b + 3; // 返回b+3的結果
語句:語句是一個行爲,一般利用一個或多個關鍵字來完成給定的任務。程序由一系列語句構成。其中流控制語句有:if/while/for等。

例如:

if(x > 0) {
      ...
    }
    for(var i = 0;i < arr.length; i ++) {
      ...
    }
    const a = 123;
表達式語句:既是表達式,又是語句

例如:

A();
    function() {}();
    delete x.b;
    b = b + 3;

綜上所述,由於a = 123 和 delete x都是表達式語句,因此分號被轉換爲逗號。而var x = {b:123}則由於是聲明語句,因此和a=123不會合並,分號不會被轉換。但var x = {b:123}和第一行var a又觸發了另一條規則,

多個var聲明能夠壓縮成一個var聲明

因此第一行和第二行會被合併爲var a,x={b:123}

4. 總結

在本文中,咱們討論了什麼是抽象語法樹,uglifyjs的壓縮原理,以及相應的壓縮規則,最終明晰了爲何代碼會被壓縮成咱們獲得的樣子,但願對你們有所幫助。

參考文獻

[1]《抽象語法樹在 JavaScript 中的應用
[2]《javascript 代碼是如何被壓縮的
[3]《[譯]JavaScript中:表達式和語句的區別
[4]《解讀uglifyJS(四)——Javascript代碼壓縮

相關文章
相關標籤/搜索