UglifyJS有個(gè)超贊的JavaScript解析器
我一直在為Jscex尋找好用的JavaScript解析器,之前我用的是Narcissus,也寫(xiě)過(guò)相關(guān)文章。不過(guò)可惜的是,Narcissus使用了SpiderMonkey的擴(kuò)展,因此它并不是用ECMAScript 3實(shí)現(xiàn)的,無(wú)法在IE 8等瀏覽器中使用。目前Jscex使用的是NarrativeJS中舊版的Narcissus,但是我并不喜歡它輸出的AST結(jié)構(gòu),使用中也發(fā)現(xiàn)高級(jí)功能里的一些bug,有些食之無(wú)味棄之可惜的感覺(jué),而改寫(xiě)新版Narcissus又必須大動(dòng)干戈。最近我接觸到了UglifyJS,發(fā)現(xiàn)它的解析器相當(dāng)不錯(cuò),性能也比Narcissus高出許多,在此介紹給大家。
UglifyJS是個(gè)JavaScript壓縮器,效果和Google Closure Compiler相比有過(guò)之而無(wú)不及。對(duì)于現(xiàn)代化的JavaScript壓縮器來(lái)說(shuō),簡(jiǎn)單的去除空白和壓縮局部變量是遠(yuǎn)遠(yuǎn)不夠的,同時(shí)需要理解代碼的語(yǔ)義,將其替換成提及更小的形式(Uglify的說(shuō)明頁(yè)上有許多描述)。這顯然需要一個(gè)JavaScript解析器。UglifyJS基于NodeJS開(kāi)發(fā),不過(guò)可以在各種支持CommonJS模塊系統(tǒng)的JavaScript引擎/平臺(tái)上運(yùn)行。如果沒(méi)有CommonJS,也只需將exports相關(guān)的代碼去掉即可。
JavaScript解析器的作用自然是將JavaScript代碼分解成AST,然后根據(jù)AST便可以做到許多有趣的事情。相同的AST可以在內(nèi)存中有不同的表現(xiàn)形式,例如之前提到我不太喜歡Jscex目前使用的舊版Narcissus,一個(gè)重要的原因便是它的AST結(jié)構(gòu)不夠友好(***的Narcissus倒不錯(cuò))。此外,雖然它提供了一些高級(jí)功能,例如標(biāo)注了每個(gè)元素在源代碼中的位置,這樣使用者就可以直接根據(jù)getSource方法獲得它對(duì)應(yīng)的源代碼——只可惜經(jīng)試驗(yàn)這個(gè)功能有bug,這迫使我還得遍歷完整的AST。
UglifyJS的JavaScript分詞器和解析器存放在源代碼的parse-js.js文件中,移植于parse-js項(xiàng)目,后者是一個(gè)用Common Lisp實(shí)現(xiàn)的類庫(kù)?,F(xiàn)在您應(yīng)該可以猜到它輸出的AST是什么表現(xiàn)形式了吧。沒(méi)錯(cuò),就是個(gè)“表”,用JavaScript來(lái)表示,就是個(gè)數(shù)組套數(shù)組。我寫(xiě)了點(diǎn)簡(jiǎn)單的代碼對(duì)其進(jìn)行格式化輸出,您可以在這里簡(jiǎn)單嘗試一下UglifyJS的解析器。這個(gè)輸出雖然簡(jiǎn)單,但對(duì)于Jscex來(lái)說(shuō)也已經(jīng)完全夠用了。
使用
打開(kāi)parse-js.js文件,您會(huì)看到這樣一些代碼:
- /* -----[ Tokenizer (constants) ]----- */
 - var KEYWORDS = array_to_hash([
 - ...
 - ]);
 - var RESERVED_WORDS = array_to_hash([
 - ...
 - ]);
 - ...
 - function parse($TEXT, exigent_mode, embed_tokens) {
 - ...
 - }
 - /* -----[ Exports ]----- */
 - exports.tokenizer = tokenizer;
 - exports.parse = parse;
 - exports.slice = slice;
 - exports.curry = curry;
 - exports.member = member;
 - exports.array_to_hash = array_to_hash;
 - exports.PRECEDENCE = PRECEDENCE;
 - exports.KEYWORDS_ATOM = KEYWORDS_ATOM;
 - exports.RESERVED_WORDS = RESERVED_WORDS;
 - exports.KEYWORDS = KEYWORDS;
 - exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN;
 - exports.OPERATORS = OPERATORS;
 - exports.is_alphanumeric_char = is_alphanumeric_char;
 - exports.set_logger = function(logger) {
 - warn = logger;
 - };
 
UglifyJS是基于CommonJS模塊機(jī)制編寫(xiě)的,這一個(gè)文件其實(shí)就是個(gè)模塊,它對(duì)外的方法通過(guò)exports暴露出來(lái)。如果我們將其作為普通的JavaScript文件引入到瀏覽器中,顯然會(huì)報(bào)“export未定義”異常。理論上說(shuō),如果定義一個(gè)exports對(duì)象,甚至去除和exports有關(guān)的代碼就能正常使用parse方法了。不過(guò)這么做也有個(gè)嚴(yán)重的問(wèn)題,那就是對(duì)根對(duì)象的“污染”實(shí)在是太嚴(yán)重了,例如在瀏覽器中所有的函數(shù),定義都出現(xiàn)在window上,再引入一些其他類庫(kù),造成沖突的可能性相當(dāng)高。
因此,我們必須對(duì)代碼進(jìn)行一些修改。幸運(yùn)的是,在JavaScript中解決這類“作用域”問(wèn)題十分容易,例如我這樣將parse-js.js的代碼包圍了起來(lái):
- var UglifyJS = {};
 - (function (exports) {
 - /* original code here */
 - })(UglifyJS);
 
這樣就解決了作用域問(wèn)題,如今我們就能訪問(wèn)UglifyJS對(duì)象上的KEYWORDS集合以及parse等成員了。
性能
然后再說(shuō)說(shuō)性能。JavaScript一直被認(rèn)為是一門執(zhí)行效率低下的語(yǔ)言——這其實(shí)是個(gè)錯(cuò)誤的觀點(diǎn)。其實(shí)從語(yǔ)言設(shè)計(jì)上說(shuō),JavaScript比Python和Ruby都要快,只不過(guò)由于歷史原因各大瀏覽器對(duì)它都不太重視而已。不過(guò)如今情況早就有所改變,在V8的帶領(lǐng)下,現(xiàn)代的JavaScript引擎執(zhí)行速度都已經(jīng)超過(guò)了目前最快的Python和Ruby實(shí)現(xiàn)。話不多說(shuō),現(xiàn)在我們就來(lái)比較一下UglifyJS的解析器與Narcissus在各瀏覽器下的表現(xiàn)吧。
測(cè)試頁(yè)面在此(http://files.zhaojie.me/demos/js-parsers/benchmark.html),您也可以自行嘗試,測(cè)試場(chǎng)景是使用兩者分別解析十次Narcissus的實(shí)現(xiàn)——大約1500行未壓縮的JavaScript代碼(值得一提的是,我試了許多壓縮后的代碼,如jquery-min.js,它們用UgilifyJS可以正常解析,而Narcissus卻解析失敗)。我使用兩臺(tái)公司配置的標(biāo)準(zhǔn)工作機(jī),測(cè)試了IE、Chrome和Firefox各兩個(gè)版本共6種瀏覽器。每個(gè)瀏覽器我都會(huì)運(yùn)行多遍測(cè)試,去處偏差大的結(jié)果,取中游數(shù)值。遺憾的是,由于條件所限,兩臺(tái)機(jī)器的操作系統(tǒng)有所不同,雖然我認(rèn)為并不會(huì)對(duì)結(jié)果有什么影響,但如果您足夠頂真,也不妨再自行評(píng)測(cè)一把。
首先我在Win 7下測(cè)試了Chrome 10、FireFox 3和IE9,結(jié)果如下:

對(duì)于UglifyJS來(lái)說(shuō),Chrome 10的表現(xiàn)***,IE 9相比略慢少許,而Firefox 3耗時(shí)則是前兩者的數(shù)倍。對(duì)于Narcissus來(lái)說(shuō),則是IE 9表現(xiàn)***,僅為Chrome 10的五分之一,和Firefox 3相比更是數(shù)量級(jí)上的領(lǐng)先。有趣的是,Chrome 10和Firefox 3下兩個(gè)解析器的耗時(shí)都是一比十左右,而IE 9下則相差無(wú)幾。
然后是Win XP下Chromium 12、Firefox 4及IE 8,結(jié)果如下:

對(duì)于UglifyJS來(lái)說(shuō),Chromium 12的表現(xiàn)依舊搶眼,勝過(guò)Firefox 4不少,不過(guò)使用Narcissus的情況則正好相反。同樣可以看出,IE 8在JavaScript引擎的性能方面已經(jīng)落后于這個(gè)時(shí)代了,不過(guò)它和IE 9、Firefox 4(以及后文的Safari)的情況類似,即UglifyJS和Narcissus的耗時(shí)并沒(méi)有太大差別。
為了便于觀察,我將兩次測(cè)試的結(jié)果放在一起(除了非正式版本的Chromium 12):

總體而言,Chrome 10、IE 9和Firefox 4為***軍團(tuán)。IE 9在UglifyJS上小負(fù)于Chrome 10,但在Narcissus上優(yōu)勢(shì)明顯;Chrome 10在UglifyJS上表現(xiàn)***,但在Narcissus卻落后較多;Firefox 4雖然都不是“***”,但差距也并不太大。至于IE 8和Firefox 3,在JavaScript的執(zhí)行效率方面的確已經(jīng)落后于這個(gè)時(shí)代了。必須承認(rèn),如今的瀏覽器大戰(zhàn)的確大大提高了各方的質(zhì)量。
此外我還測(cè)試了公司iMac上的Chrome 10、Firefox 3以及Safari 5,在此列出結(jié)果:

雖然瀏覽器的表現(xiàn)各有高低,差距也有所不同,但可以確定的是,UglifyJS解析器的性能的確比Narcissus要高。因此,我打算在接下來(lái)幾天里用UglifyJS替換掉目前Jscex里使用的Narcissus。
總結(jié)
由于前端開(kāi)發(fā)和JavaScirpt的流行,越來(lái)越多的人開(kāi)始用JavaScript做一些有趣的事情。我很不喜歡如今許多所謂的前端實(shí)踐,糾纏于大量的hack以及各種瀏覽器的表現(xiàn),甚至是JavaScript里某種特定寫(xiě)法的性能更高——例如,居然有消息稱,對(duì)于字符串連接操作來(lái)說(shuō),a += b的性能比a = a + b要高(或反之)。在我看來(lái)這些東西是最無(wú)用的,知道了又如何?隨著瀏覽器更新?lián)Q代,這些“經(jīng)驗(yàn)”瞬間就毫無(wú)作用了。
這也是我為什么喜歡玩JavaScript,卻死也不愿去做前端開(kāi)發(fā),尤其是HTML、CSS。同樣,如IE 6這種瀏覽器在我眼中也是必須消滅的東西。
 
原文鏈接:http://blog.zhaojie.me/2011/04/uglifyjs-has-a-good-javascript-parser.html
【編輯推薦】















 
 
 








 
 
 
 