故事起源於一個很小問題,我寫了個程式碼,被質疑有問題:簡化之後大概如下:
let a; const x = { b: 123 }; a = 123, delete x
被質疑的主要原因是第三行a=123的後面為什麼是逗號,不是分號。坦白來說,我是簡單的手誤,將分號錯寫成了逗號。但是感覺貌似應該也沒有什麼問題,畢竟uglifyjs會將某些語句進行合併,將分號變成逗號。繼而再一想,uglifyjs是如何來進行程式碼壓縮的、它是如何知道該合併哪些語句,不合並哪些語句的、 它又有哪些合併規則?於是有了本文。
1. AST(抽象語法樹)
要想了解JS的壓縮原理,需要首先了解AST。
抽象語法樹:AST(Abstract Syntax Tree),是原始碼的抽象語法結構的樹狀表現形式,這裡特指程式語言的原始碼。樹上的每個節點都表示原始碼中的一種結構。之所以說語法是「抽象」的,是因為這裡的語法並不會表示出真實語法中出現的每個細節。
舉個例子:
從上面兩個例子中,可以看出AST是原始碼根據其語法結構,省略一些細節(比如:括號沒有生成節點),抽象成樹形表達。抽象語法樹在電腦科學中有很多應用,比如編譯器、IDE、壓縮程式碼、格式化程式碼等。[1]
2. 程式碼壓縮原理
瞭解了AST之後,我們再分析一下JS的程式碼壓縮原理。簡單的說,就是
1. 將code轉換成AST
2. 將AST進行優化,生成一個更小的AST
3. 將新生成的AST再轉化成code
PS:具體的AST樹大家可以在astexplorer上線上獲得
babel,eslint,v8的邏輯均與此類似,下圖是我們引用了babel的轉化示意圖:
以我們之前被質疑的程式碼為例,看看它在uglify中是怎麼樣一步一步被壓縮的:
// 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的壓縮規則。
3. 程式碼壓縮規則
由於uglifyjs的程式碼壓縮規則很多,我們這裡只分析與本文中相關的部分:
uglifyjs的全部壓縮規則可以參見:《[解讀uglifyJS(四)——Javascript程式碼壓縮](https://rapheal.sinaapp.com/2014/05/22/uglifyjs-squeeze/#more-705)》
連續的"表示式語句"可以合併成一個逗號表示式
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程式碼壓縮》