Google Closure Compiler高級壓縮混淆Javascript代碼


 

一、背景

前端開發中,特別是移動端,Javascript代碼壓縮已經成為上線必備條件。

如今主流的Js代碼壓縮工具主要有:

1)Uglify http://lisperator.net/uglifyjs/
2)YUI Compressor http://developer.yahoo.com/yui/compressor/
3)Google Closure Compiler https://developers.google.com/closure/compiler/

自從jQuery等流行庫使用Uglify作為壓縮工具,Uglify慢慢流行起來,如今也是很多開發工具(框架)默認使用的Js壓縮工具,比如百度的Fis以及絕大部分的Yeoman腳手架等。YUI Compressor也逐漸被Uglify所替代。

Uglify的壓縮策略較為安全,所以不會對源代碼進行大幅度的改造,壓縮相對較為保守。所以將通過Uglify壓縮后的代碼格式化之后,還是大致能看明白。

而Google Closure Compiler(以下簡稱GCC)的高級壓縮則是完全重新改造JS代碼,去除代碼中一些可以直接輸出運行結果的邏輯,去除根根本不會執行到的JS代碼,從而壓縮效率會更高。

 

二、GCC三種壓縮模式

GCC提供三種壓縮模式:

1)Whitespace only

2)Simple

3)Advanced

我們以這段簡單的代碼為例

function sayHello(name) {
  alert('Hello, ' + name);
}
sayHello('binnng');

分別使用這三種壓縮模式進行壓縮:

Whitespace only

function sayHello(name){alert("Hello, "+name)}sayHello("binnng");

發現只是簡單的去除空格換行注釋。

Simple

function sayHello(a){alert("Hello, "+a)}sayHello("binnng");

Whitespace only要高端一點,在其基礎上,還對局部變量的變量名進行縮短。這也是其他壓縮工具所使用的壓縮方式,如Uglify等,也是最為主流的壓縮方式。比較安全。

Advanced

alert("Hello, binnng");

會發現,Advanced級別的壓縮改變(破壞)了原有代碼結構,直接輸出了代碼最終運行結果,可見的確是分析重寫破壞,但是對代碼壓縮做到了極致,極大的減少了代碼量。

注意的地方

正因為GCC是這樣的破壞性壓縮工具,所以使用起來得異常小心,稍微不規范可能就會引起壓縮報錯或者壓縮成功后卻不能正常運行。那么,如果要使用Advanced級別壓縮,要注意哪些呢?

以下所有未指名級別的GCC壓縮均為Advanced級別。

GCC會對變量的屬性名也進行壓縮

var data = {
  user: "binnng",
  age: "18"
};

if ("user" in data) {
  alert(data.age);
}


//經過Uglify壓縮:
var data={user:"binnng",age:"18"};"user"in data&&alert(data.age);

//經過GCC壓縮:
var a={b:"binnng",a:"18"};"user"in a&&alert(a.a);

會發現GCC壓縮時,將變量的屬性名也縮短,代碼量減少了,但是卻帶來了問題,會發現以上GCC壓縮后的代碼運行其實是不正確的。因為屬性名縮短改變后,data已經不再擁有名為user的屬性了。

那如何解決呢?

var data = {
  user: "binnng",
  age: "18"
};

if (data.user) {
  alert(data.age);
}


//GCC壓縮:
alert("18");

直接輸出了正確的結果。

如果不想讓GCC壓縮屬性名,比如在Ajax請求發送給后端接口的時候,可以將屬性名用雙引號包裹:

var data = {
  "user": "binnng",
  "age": "18"
};

var ajax = function(url, data, callback) {
  (new window.XMLHttpRequest()).send(data);
  // ...
}; 

ajax("//baidu.com", data, function(res) {console.log(res)});


//GCC壓縮后:
(new window.XMLHttpRequest).send({user:"binnng",age:"18"});

原封不動的保留了后端需要的參數名。

全局變量要顯式掛載在window下

var foo = "1234";
alert(widnow.foo);
//經過GCC壓縮后:

alert(window.a);

有點莫名其妙。。因為在GCC中,不再認可隱式全局變量,所以上面的代碼中在GCC眼里,foo是沒有掛載到window下的,所以下文的window.foo其實是未定義的。

要解決這個問題,必須將foo顯式掛載到window下:

window.foo = "1234";
alert(widnow.foo);
//這樣經過壓縮后:

window.a="1234";alert(widnow.a);

這時候可能就會疑問,為何不直接壓縮成alert("1234")呢?這是因為GCC不會去除掛載在window下的變量

必要時導出變量函數等

window.btnClick = function() {
  alert("clicked");
};
//以上的代碼經過壓縮后成為:

window.a=function(){alert("clicked")};

此時,如果HTML中存在如下代碼,就會報錯。

<a onclick="btnClick()">點我</a>
//這時候就需要導出函數:

window["btnClick"] = function() {
  alert("clicked");
};

用雙引號包裹后,btnClick就保留了下來。在構造函數中,尤其需要注意導出,例如:

var Animal = function(name) {
  this.name = name;
};
Animal.prototype.eat = function() {
  alert(this.name + " is eating!");
};

以上的代碼經過GCC壓縮后,什么都沒剩下,因為GCC認為,代碼中的代碼都沒有執行,屬於dead code。既然這么寫了,那肯定是需要它,提供給別人外部調用什么的,這時候就需要這么導出:

var Animal = function(name) {
  this.name = name;
};
Animal.prototype.eat = function() {
  alert(this.name + " is eating!");
};

window["Animal"] = Animal;
Animal.prototype["eat"]= Animal.prototype.eat;
//經過壓縮后:

function a(b){this.name=b}a.prototype.a=function(){alert(this.name+" is eating!")};window.Animal=a;a.prototype.eat=a.prototype.a;

這時候,Animal這個方法成功的保留了下來。

還有,在使用JSONP方式獲取服務端數據的時候,也一定要導出callback方法名:

var jsonpCb = function() {
  //...
};

getScript("/api/data?callback=jsonpCb");

// 導出,否則jsonpCb會被壓縮掉不能被識別
window["jsonpCb"] = jsonpCb;

所有業務代碼合並壓縮

a.js

var getName = function() {return "binnng"};
b.js

alert(getName());

如果單獨壓縮a.jsb.js就會出問題,結果會是a.js中什么都沒有,b.jsgetName方法未定義(undefined)。正確的做法則是,兩者合並再進行壓縮。

 

三、GCC三種運行方式

1)、”Closure Compiler Service UI”:GCC提供的在線壓縮方式,只需導入文件路徑或是直接復制粘貼文件內容,即可實現壓縮。在線壓縮網站為:http://closure-compiler.appspot.com/home詳細步驟如下:

      A.打開頁面,在”Add a URL”文本框中輸入文件路徑,然后點擊”add”。或者直接把文件內容復制到下邊的文件域中。

      B.在”Optimization”中選擇壓縮模式,默認為”Simple”模式。

      C.點擊”Compile”按鈕,進行壓縮。如圖,右側這顯示壓縮完的文件信息。

      D.可以選擇復制下面的文本內容粘貼到壓縮前原文件,或者右鍵點擊”default.js”-->”鏈接另存為”,直接將壓縮完的文件重命名並保存到本地。

      2)、”Closure Compiler Service API”:通過自定義HTML頁面發送請求的方式來獲取壓縮文件。與”Closure Compiler Service UI”相比,它可以建立自己的工具,創建自己的工作流。而”Closure Compiler Service UI”只適合壓縮少量的代碼和文件。詳細步驟如下:

      A.創建一個名為”closure_compiler_test.html”的HTML頁面,用於發送post請求。代碼如下:

 <html>

  <body>

    <form action="http://closure-compiler.appspot.com/compile" method="POST">

    <p>Type JavaScript code to optimize here:</p>

    <textarea name="js_code" cols="50" rows="5">

    function hello(name) {

      // Greets the user

      alert('Hello, ' + name);

    }

    hello('New user');

    </textarea>

    <input type="hidden" name="compilation_level" value="WHITESPACE_ONLY">

    <input type="hidden" name="output_format" value="text">

    <input type="hidden" name="output_info" value="compiled_code">

    <br><br>

    <input type="submit" value="Optimize">

    </form>

  </body>

</html>

     B.用瀏覽器打開該HTML文件,點擊”Optimize”按鈕提交,並會返回壓縮后文件。然后復制粘貼保存。

     注意:每個請求至少必須發送以下參數:

a.”js_code”或”code_url”:這個參數的值指示要編譯的JavaScript。必須至少包含其中一個參數,並且可以同時包括。”js_code”必須是包含javascript的字符串。”code_url”必須是包含js文件的url地址。

b.”compilation_level”:這個參數表示文件壓縮的模式,有三個值,分別為”whitespace_only”,”simple_optimizations”,”advanced_optimizations”。上文用的是”whitespace_only”,默認為”simple_optimizations”。

c.”output_info”:此參數的值指示要從編譯器獲取的信息類型。有四個值,分別為”compiled_code”,”warnings”,”errors”,”statistics”。上文用的是”compiled_code”,返回的是壓縮好的文件。

d.”output_format”:此參數表示輸出的格式。有三個值,分別為”text”,”json”,”xml”,默認為”text”。

    更多關於API的使用請參照官網。

      3)、”Closure Compiler Application”:用官網提供的客戶端進行代碼壓縮。從官網下載壓縮包,地址為:https://dl.google.com/closure-compiler/compiler-latest.zip。本客戶端是在Java環境下運行的,所以下載之前應安裝JDK。安裝JDK步驟參考:http://jingyan.baidu.com/article/2d5afd6993a6db85a2e28e9f.html

      詳細步驟如下:

      A.創建一個名為”closure-compiler”的文件夾。

      B.把下載的壓縮包解壓到”closure-compiler”下。

      C.把要壓縮的js文件也放到當前目錄下。

      D.用命令窗口打開文件夾中”README.md”文件。打開如下

      

系統會自動進入到當前文件位置。如果沒有打開,可以嘗試如下做法:

      a.按”windows+r”鍵,進入“運行”,輸入”cmd”,進入命令窗口。

      b.然后輸入剛剛創建的”closure-compiler”文件夾的路徑。例:”F:\workspace\closure-compiler”,則如下:

     E.然后輸入”java -jar 目錄下.jar文件--js 目錄下.js文件--js_output_file 壓縮完保存的文件名”,例:”java -jar closure-compiler-v20161201.jar --js setting.js --js_output_file setting.min.js”,生成的文件也會保存在當前的目錄下(closure-compiler文件夾)。生成的文件的壓縮模式默認為”SIMPLE”,如果要改變其他的壓縮模式,在上邊的的語句中添加”--compilation_level=ADVANCED”,值可以為”WHITESPACE_ONLY”,”SIMPLE”,”ADVANCED”。

     

如果想了解更多使用方法,請輸入”java -jar closure-compiler-v20161201.jar --help”。

 

四、結語

GCC的高級壓縮(Advanced)非常強大,對代碼壓縮做到了極致,但是其對代碼書寫要求也比較嚴格,並且破壞性壓縮也被很多開發者所詬病。

但是稍加注意,嚴格規范自身代碼風格,了解GCC壓縮方式原理,利用好GCC高級壓縮,一定會大大減少JS的體積,從而大幅度的提升前端代碼性能。

另外,GCC像其他壓縮工具一樣,也有GruntGulp構建組件,可以很方便的去使用它。

 

轉載自:

  1)https://segmentfault.com/a/1190000002575760

  2)https://blog.csdn.net/nh18304030935hn/article/details/54571682

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM