天天躁日日躁狠狠躁AV麻豆-天天躁人人躁人人躁狂躁-天天澡夜夜澡人人澡-天天影视香色欲综合网-国产成人女人在线视频观看-国产成人女人视频在线观看

Google Closure Compiler 高級(jí)模式及更多思考

  前言

  Google Closure Compiler 是 Google Closure Tools 的一員,在 2009 年底被 Google 釋出,早先,有 玉伯 的 Closure Compiler vs. YUICompressor,主要就壓縮率上進(jìn)行了對(duì)比,另外有 承玉 的 應(yīng)用 closure compiler 高級(jí)模式,對(duì) CC 的高級(jí)模式做了些介紹。

  本文將詳細(xì)介紹 CC 的高級(jí)模式部分,更重要的是,闡述 CC 高級(jí)模式背后的思考

  CC 是真正的編譯器

  Closure Compiler 和 YUICompressor 并不是同類產(chǎn)品,雖然 CC 和 YC 同樣產(chǎn)出壓縮后的 JS 文件,但是 YC 只做了詞法上的掃描,而 CC 并不只是一個(gè) compressor 那么簡(jiǎn)單,器如其名,它是一個(gè)compiler。

  對(duì)于一個(gè) compiler,一般地,它需要做到:

  1. 檢查源文本中語(yǔ)法、語(yǔ)義、語(yǔ)用上的錯(cuò)誤;
  2. 根據(jù)分析產(chǎn)出物(符號(hào)表、語(yǔ)法樹等)產(chǎn)出目標(biāo) / 中間代碼;
  3. 優(yōu)化。

  代碼錯(cuò)誤一般來(lái)自三個(gè)方面:

  1. 語(yǔ)法(Syntax)

    表示構(gòu)成語(yǔ)言句子的各個(gè)記號(hào)之間的組合規(guī)律。大體上,parser / interpreter 在詞法分析和語(yǔ)法分析階段,產(chǎn)生符號(hào)表、語(yǔ)法樹等分析產(chǎn)出物,具體見編譯原理教科書……

    語(yǔ)法上的錯(cuò)誤,如:

    doSomething(;) // SyntaxError: Unexpected token ;

    根據(jù)語(yǔ)法規(guī)則,在非 for 語(yǔ)句中的 ; 意義是分隔符,而分隔符前的 ( 并沒有配對(duì)),因此報(bào)錯(cuò)。

  2. 語(yǔ)義(Semantics)

    表示各個(gè)記號(hào)的特定含義(各個(gè)記號(hào)和記號(hào)所表示的對(duì)象之間的關(guān)系)。compiler 需要根據(jù)語(yǔ)義分析產(chǎn)出中間代碼,對(duì)于不產(chǎn)生中間代碼的語(yǔ)言如 JS,則在運(yùn)行時(shí)的解釋期間指出錯(cuò)誤。

    語(yǔ)義上的錯(cuò)誤,如:

    0 = {}; // ReferenceError: Invalid left-hand side in assignment

    根據(jù)賦值運(yùn)算符 = 的意義,左操作數(shù)不能為字面量,所以雖然這個(gè)賦值語(yǔ)句包含了必需的左操作數(shù)、運(yùn)算符、右操作數(shù),仍然出錯(cuò)。

  3. 語(yǔ)用(Pragmatics)

    表示在各個(gè)記號(hào)所出現(xiàn)的行為中,它們的來(lái)源、使用和影響。

    語(yǔ)用上的錯(cuò)誤,如:

    doSomething(); // ReferenceError: doSomething is not defined

    在這里直接調(diào)用了一個(gè)未定義的函數(shù),導(dǎo)致出錯(cuò)。在一些其他場(chǎng)景中,雖然程序運(yùn)行正確無(wú)誤,但是仍然可以優(yōu)化(這種優(yōu)化并不是技巧上的),比如:

function doSomethingElse() {}(function() { return; doSomethingElse(); // No Exception but Redundant: Unreachable code})();

  在這里,doSomethingElse 函數(shù)之前由于有 return,因此這個(gè)函數(shù)調(diào)用將永遠(yuǎn)不能執(zhí)行,這種冗余代碼對(duì)整個(gè)程序來(lái)說(shuō)毫無(wú)用處,可以去掉。

  對(duì)于 Closure Compiler 來(lái)說(shuō),它處理的對(duì)象是 js,不需要產(chǎn)生其他中間代碼或匯編代碼 / 機(jī)器碼,因此輸出的還是 js,但是是經(jīng)過(guò)分析的、優(yōu)化后的 js;另外,它也可以選擇輸出 parse tree(使用–print_tree 參數(shù)),所以,CC 的確完成了一個(gè)編譯器需要實(shí)現(xiàn)的功能。

  CC 功能概述

  在詳細(xì)討論 CC 的高級(jí)模式前,還是簡(jiǎn)明介紹一下功能體系。

  編譯級(jí)別

  CC 的 compilation_level 包括三個(gè)級(jí)別:

  1. WHITESPACE_ONLY

    只刪除空白、注釋。

  2. SIMPLE_OPTIMIZATIONS

    在 WHITESPACE_ONLY 基礎(chǔ)上將局部變量和參數(shù)轉(zhuǎn)成短名稱。

  3. ADVANCED_OPTIMIZATIONS

    更加激進(jìn)的重命名、移除垃圾代碼、內(nèi)聯(lián)函數(shù)。

  可以看到,SIMPLE_OPTIMIZATIONS 級(jí)別的 CC,和 YC 無(wú)異,沒做什么真正的編譯工作,所以說(shuō),使用了高級(jí)模式的 CC 才是四肢健全的 CC 。

  約束條件

  使用 CC 有一定約束條件,這影響到我們的編碼風(fēng)格:

  1. WHITESPACE_ONLY

    • 不認(rèn)可 JS 1.5 以上版本的語(yǔ)言特性
    • 不保留注釋
  2. SIMPLE_OPTIMIZATIONS

    • 完全禁用 with 和 eval
    • 字符串中引用的函數(shù)名 / 參數(shù)名不會(huì)改動(dòng)(CC 不改動(dòng)所有字符串)
  3. ADVANCED_OPTIMIZATIONS 模式下的約束放到下文詳述

  注解

  Annotations 也是 CC 的重要組成部分,使用 JSDoc 風(fēng)格,用以輔助高級(jí)模式下的編譯,下文詳述。

  使用 CC 高級(jí)模式

  在 CC 下,啟用高級(jí)模式的方法是加入?yún)?shù) --compilation_level ADVANCED_OPTIMIZATION。

  作為一個(gè) compiler,CC 的高級(jí)模式下,額外的優(yōu)化政策是:

  1. 更激進(jìn)的重命名,如 obj.property 改為 a.b,將深度過(guò)高的命名空間平坦化等;
  2. 移除垃圾代碼,如刪除未被調(diào)用的方法定義,警告邏輯死角(return 后的語(yǔ)句等);
  3. 將函數(shù)內(nèi)聯(lián),如 a call b, b call c,a(),那么直接執(zhí)行 c()。

  要達(dá)到高級(jí)模式的預(yù)期優(yōu)化效果,開發(fā)者必須對(duì)自己做一些約束,因?yàn)?js 是弱類型、動(dòng)態(tài)性的。否則
js 的這種靈活將使 compiler 無(wú)能為力。

  總體上,這種約束包括限定某些 js 編碼風(fēng)格,以及使用相應(yīng)的 JSDoc 注解。

  以下詳述具體的約束以及代碼的檢查 / 優(yōu)化效果:

  強(qiáng)類型的模擬

  • @param 和 @type 中定義的類型會(huì)在編譯期間得到檢查,同樣避免了在運(yùn)行時(shí)檢查,提高性能。

  • @const 標(biāo)記常量,當(dāng)常量被寫時(shí)會(huì)報(bào)錯(cuò)。

  • 模擬枚舉,將同類可枚舉常量定義為一個(gè)對(duì)象字面量,使用 @enum 標(biāo)記:

    var STATUS = { LOADING: 3, COMPLETE: 4};

    編譯結(jié)果中 STATUS.LOADING 會(huì)被直接替換為 3,其實(shí)完全模擬了 C 等語(yǔ)言中的枚舉。

  • 使用 @constructor 標(biāo)注函數(shù)為構(gòu)造器,它僅能被實(shí)例化,而不可用作普通方法,甚至是工廠方法,CC 會(huì)確保構(gòu)造器被合法使用,否則報(bào)錯(cuò)。這樣確保開發(fā)者不必在運(yùn)行時(shí)判斷,構(gòu)造器函數(shù)到底以怎樣的形式被調(diào)用。

  • 在表達(dá)式中也可以使用 @type 來(lái)限定類型,這對(duì)于 JSON 特別有用,如

    var data = /** @type {UserModel} */({ firstName : 'foo', lastName : 'bar'});

    在這里 UserModel 是個(gè)構(gòu)造器,也可以使用 @typedef 來(lái)自定義復(fù)雜的數(shù)據(jù)類型。

  域可見性的模擬

  • 使用 @private 標(biāo)注私有域,私有域被外部引用會(huì)報(bào)錯(cuò)。開發(fā)者也可以按照“國(guó)際慣例”給私有域加上_ 前綴或后綴,以提醒自己 / 協(xié)作者這是一個(gè)私有域,@private 注解用來(lái)告訴 CC;這樣,開發(fā)者可以不必使用諸如老道的“模塊模式”等技巧來(lái)真正地隱藏私有變量,將檢查工作丟給CC,讓開發(fā)盡可能樸實(shí)簡(jiǎn)單。

  • 類似有 @protect

  類系統(tǒng)的模擬

  • 使用 @extends 標(biāo)注繼承關(guān)系,繼承體系會(huì)被優(yōu)化。

  • 使用 @interface 標(biāo)注接口,接口是類似 function ThisIsAInterface(obj) {}的函數(shù)體為空的構(gòu)造器定義,編譯后將移除其相關(guān)代碼。同時(shí),標(biāo)注 @implements 的構(gòu)造器必須實(shí)現(xiàn)implemented 的接口的所有方法(正如其他 OO 語(yǔ)言一樣),否則,CC 報(bào)錯(cuò)。這同樣簡(jiǎn)化了接口 / 實(shí)現(xiàn)的約束,靠 CC 來(lái)保證實(shí)現(xiàn)關(guān)系的可靠性。

  條件編譯的模擬

  • 使用 @define 標(biāo)記狀態(tài)開關(guān),適用于調(diào)試 logger 等 開發(fā) / 發(fā)布 狀態(tài)需要分離的模式。
    可以在編譯時(shí)指定參數(shù)來(lái)標(biāo)識(shí) define 參數(shù)的狀態(tài)。這其實(shí)就是一個(gè)條件編譯,真給力……

  對(duì)象平坦化及屬性名縮減

  • 對(duì)象屬性會(huì)被編譯為單變量,比如 foo.bar to foo$bar,這種標(biāo)記方法看起來(lái)很像 Java 中被編譯出來(lái)的內(nèi)部類~~之后 foo$bar 被進(jìn)一步縮短。對(duì)象之所以能被平坦化是因?yàn)樵?nbsp;js 中對(duì)象可以看做是一群引用 / 原始數(shù)據(jù)類型的容器。

  • 但是,js 對(duì)象實(shí)際上更復(fù)雜,所以被平坦化后會(huì)帶來(lái)一些副作用,比如如果在對(duì)象(字面量)中使用 this 指針,則編譯后的結(jié)果會(huì)導(dǎo)致 this 指向錯(cuò)誤。所以 Google 建議僅在 constructor 和 prototype methods 中使用 this,這意味著,在所謂類單例(對(duì)象字面量)和類的靜態(tài)方法(綁定到constructor 上的函數(shù))中都避免使用 this 指針。

  • 在縮減對(duì)象屬性 / 方法的名稱長(zhǎng)度時(shí),有另外一個(gè)注意點(diǎn),那就是必須始終使用 dot syntax(.運(yùn)算符),而不使用 quoted string([] 運(yùn)算符),除非索引名是一個(gè)變量。這是因?yàn)?CC 始終不處理字符串中的內(nèi)容,所以,var o = { longName: 0 }; o["longName"] 會(huì)被翻譯為var a = { b: 0 }; a["longName"] 導(dǎo)致出錯(cuò)。實(shí)在想使用 quoted string,則在定義的時(shí)候也要使用 quoted string。

  • 對(duì)于全局變量,如果出現(xiàn)以 window.property 的形式引用的,必須始終定義為 window.porperty 形式:

    window.property = 1;var property = 1; // wrong!

    否則也會(huì)杯具,CC 可不會(huì) window.property 翻譯為 window.a。

  垃圾代碼的移除

  • 一個(gè)函數(shù)聲明卻未被調(diào)用時(shí),默認(rèn)地,聲明體將被干掉。

  • 在這種機(jī)制下,如果一個(gè)方法是以 for in 的形式調(diào)用的,那么原方法也會(huì)被干掉,因?yàn)檫@種動(dòng)態(tài)特征使得 CC 無(wú)法清楚方法是否確實(shí)在 for in 的時(shí)候被調(diào)用了。

  • 對(duì)于一些 unreachable 的代碼,CC 將報(bào)警告。

  • 如果要產(chǎn)出一份被調(diào)用的公共接口,例如庫(kù),使用稱作 export 的方法將函數(shù)導(dǎo)出,防止函數(shù)定義被
    CC 回收。具體的做法是將函數(shù)綁定到某個(gè)容器,比如:

    function displayNoteTitle(note) { alert(note['myTitle']);}// Store the function in a global property referenced by a string:window['displayNoteTitle'] = displayNoteTitle;

    對(duì)于需要 export 的函數(shù),均使用 quoted string 風(fēng)格。

  背后的思考

  根據(jù)以上高級(jí)模式優(yōu)化的行為分析可知,CC 附加給開發(fā)者的約束主要有:

  1. 強(qiáng)制以強(qiáng)類型的靜態(tài)語(yǔ)言風(fēng)格編寫 js,將關(guān)注點(diǎn)從運(yùn)行時(shí)的動(dòng)態(tài)技巧轉(zhuǎn)移到組織代碼、編寫邏輯
    本身。而可能由弱類型系統(tǒng)和動(dòng)態(tài)特征產(chǎn)生的問(wèn)題和風(fēng)險(xiǎn)則交給 CC,即通過(guò)開發(fā)者與 CC 達(dá)成一種

    編碼約定而規(guī)避掉。

  2. 嚴(yán)格要求區(qū)分面向開發(fā)者的代碼面向機(jī)器的代碼。

    雖然不像 C 等語(yǔ)言會(huì)編譯產(chǎn)生目標(biāo)代碼,但是 CC 在一定程度上也生成了面向機(jī)器的 js,包括壓縮空白、縮減標(biāo)識(shí)符、條件編譯和冗余代碼去除。這和第一點(diǎn)其實(shí)是一脈相承的,同樣要求開發(fā)者將關(guān)注點(diǎn)轉(zhuǎn)移到開發(fā)本身。

  3. 使用規(guī)范化的接口方式。

    這不僅包括要求開發(fā)者使用恰當(dāng)?shù)?annotation(extend, interface, …),同時(shí)也給整個(gè) OO-JS 打下了一個(gè)框架,開發(fā)者必須使用同樣的模式進(jìn)行 OO 編碼。另外,要求使用 export 技術(shù)統(tǒng)一導(dǎo)出公共接口更強(qiáng)化了這一點(diǎn)??傊?,這一點(diǎn)進(jìn)一步限定了開發(fā)者的編碼風(fēng)格,但是帶來(lái)的好處是明顯的:可讀、可控、一致性。

  曾經(jīng)有讀過(guò) Closure Library 源碼的同學(xué)評(píng)論道:

Google 根本不懂怎么寫 Javascript!代碼里面各種冗余,并且充滿了 Java 的味道!

  當(dāng)時(shí)確實(shí)也有這種感覺,比如 Google 把 if(foo) 寫作 if(foo != undefined) 等等。

  Javascript 固然充滿了豐富的動(dòng)態(tài)特征,而且很多特性非常優(yōu)雅,能夠讓代碼簡(jiǎn)潔精悍,或者構(gòu)造出一些
令人驚嘆的技巧,但是也會(huì)產(chǎn)生一些副作用:

  • 首要的問(wèn)題是可讀性,靜態(tài)的東西容易一目了然,動(dòng)態(tài)的東西需要經(jīng)過(guò)一番運(yùn)算才能得出結(jié)論。
    比如 js 中的極晚綁定,再比如標(biāo)識(shí)符運(yùn)行時(shí)重寫。

  • 其次的問(wèn)題是執(zhí)行性能。一個(gè)比較經(jīng)典的眾 js 工程師都在使用的技巧就是“模擬函數(shù)重載”——
    在函數(shù)體內(nèi)判斷 arguments 的特征,從而對(duì)應(yīng)給出不同的邏輯。由于缺乏強(qiáng)類型,js 本身不能具備
    真正的重載,但是運(yùn)行時(shí)的判斷在帶來(lái)靈活性的同時(shí),必然會(huì)多出很多模擬重載的邏輯,降低性能。

  在今年的 D2 大會(huì)上,Hedger 同學(xué)指出,大多數(shù) js 開發(fā)者像是個(gè) ninja(忍者),他們身懷絕技、神鬼莫測(cè),單兵作戰(zhàn)還可以,但是一旦碰到 army(軍隊(duì),比如 Google 團(tuán)隊(duì)這樣的 )就是個(gè)悲劇。

  我比較欣賞這個(gè)比喻,大團(tuán)隊(duì)要良好地協(xié)作,必需遵循一定的規(guī)范和限制,優(yōu)先保證可讀性和一致性,與此同時(shí)
失去的是奇技淫巧、自由靈活。所以采用何種編程風(fēng)格、理念,需要具體問(wèn)題具體分析…………

  至少,目前 CC 提供了一個(gè)好的思路,它的高級(jí)模式推崇的編程風(fēng)格也是很值得嘗試、借鑒的。

  最后附上 CC 的常用命令選項(xiàng)……選項(xiàng)實(shí)在是有夠多……

  CC 常用命令選項(xiàng)

  • –charset VAL 對(duì)所有文件定義的編碼格式
  • –compilationlevel [WHITESPACEONLY | SIMPLEOPTIMIZATIONS | ADVANCEDOPTIMIZATIONS]
    設(shè)定編譯級(jí)別
  • –debug 開啟 debug 選項(xiàng)
  • –define (–D, -D) VAL 設(shè)定文件中使用 @define 標(biāo)注的開關(guān)值,即條件編譯
  • –externs VAL 編譯代碼需要調(diào)用未編譯的代碼時(shí),使用它
  • –formatting [PRETTYPRINT | PRINTINPUT_DELIMITER] 格式化輸出
  • –js VAL 輸入文件,多指定多個(gè),將會(huì)被合并
  • –jsoutputfile VAL 輸出文件,如果不指定的話,直接輸出到 standard output 流
  • –module VAL 定義模塊
  • –output_manifest VAL 打印編譯文件清單
  • –print_tree 打印語(yǔ)法分析樹
  • –warning_level [QUIET | DEFAULT | VERBOSE] 設(shè)定報(bào)錯(cuò)模式

it知識(shí)庫(kù)Google Closure Compiler 高級(jí)模式及更多思考,轉(zhuǎn)載需保留來(lái)源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 国产精品亚洲第一区二区三区 | 大桥未久电影在线 | 国产午夜伦伦伦午夜伦 | 久久国产亚洲电影天堂 | 亚洲中文字幕永久在线 | 超碰97 总站 中文字幕 | 久久夜色噜噜噜亚洲AV0000 | av狼新人开放注册区 | 国产AV天堂一区二区三区 | 快穿女主有名器的H纯肉黄暴拉文 | 成人短片迅雷下载 | 夜色福利院在线观看免费 | 亚洲日本欧美国产在线视 | 在线视频 亚洲 | 幼儿交1300部一区二区 | 成人精品视频在线观看 | 高清毛片一区二区三区 | 久久99蜜桃精品麻豆 | 亚洲在线国产日韩欧美 | 色色色久久久免费视频 | 偷拍国产精品在线播放 | 久久精品视频在线看 | 九热这里只有精品 | 日韩美女爱爱 | 国产国产乱老熟视频网站 | 欧美日韩视频一区二区三区 | 国产AV天堂亚洲AV麻豆 | 依人青青青在线观看 | 99re热有精品国产 | 亚洲无吗精品AV九九久久 | 国产色精品久久人妻99蜜桃麻豆 | 亚洲国产精品嫩草影院久久 | 娇妻归来在线观看免费完整版电影 | 国产精品涩涩涩视频网站 | 鸭子玩富婆流白浆视频 | 久久免费看少妇高潮A片JA | 国产永不无码精品AV永久 | 国产欧美日韩综合精品一区二区 | 亚洲欧美日韩一级特黄在线 | 九九精彩视频在线观看视频 | 99久久国产宗和精品1上映 |