YUI3設計中的激進和妥協
[2] 顆粒化 or http請求數
相信每個前端工程師都有自己喜愛的javascript框架,說情感也好,道信仰也罷,javascript框架帶給人的不僅僅是便捷的開發,更有一種純粹的邏輯美感,不管是jquery曼妙的簡潔,還是yui魔術般的沙箱,都使我們產生無窮的想象。然而,js框架卻又必然無法做到面面俱到的完美無瑕,比如jquery在OO方面做出的讓步,以及yui在性能上做的犧牲,無不給人傳達一種缺憾美、一種理想的現實主義。今天,我們來看看yui3在框架設計中的這些犧牲和讓步,以便讓我們更加深刻的理解yui3的全貌,并將其優勢發揮至最佳。
1、種子的一步到位 or 顆粒化
所謂種子一步到位是指只要在頁面引入一個種子文件的script標簽,比如prototype和jquery,只要引入一個prototype.js或jquery.js就可以了,他們將各自對dom操作和event的封裝等等都囊括進一個文件中,并盡力將其做到最小,這樣做的好處是顯而易見的,使用框架非常簡單。然而yui將這些功能做了級別劃分和顆粒化設計,從概念上抽象出來“核心”、“工具”和“組件”,每個小功能放在一個文件當中,需要的時候則要自行去引用,yui文檔中給出的大量demo都采用這種方法,這種設計顯然不像jquery那樣對初學者友好,而且使用起來不夠傻瓜,為了實現一個小功能,甚至要引入三四個js文件。yui這樣做的原因有兩個,一是yui實在太大,把所有功能都搞進一個文件中確實有點不靠譜,二是為其動態加載的框架設計做鋪墊。
2、手動引入 or 動態加載
往頁面中寫js的傳統方法是,直接將js文件作為script標簽路徑寫在頁面中,使用yui也可以這樣引入頁面,但yui更推薦使用loader進行動態加載。動態加載腳本的淵源很復雜,目前來看主要原因有三,其一,頁面中手寫js標簽無論如何都會占用一個http請求,即使這個請求是一個304,動態加載的文件緩存后則不必發起真實的http請求,其二,動態加載可以實現按需加載,而且可以根據js文件之間的依賴進行去重和排序,手寫標簽加載js文件則必須讓開發者去額外關注一下文件的排序、重復等等,其三,動態加載有利于頁面代碼的語義化,這使得開發者只關心“需要什么”,而不用去在意“如何得到”。當項目變得越發臃腫,維護成本越來越高的時候,這中小技巧會有不小的好處的。
3、邏輯啟動的單一入口 or 沙箱
我們在頁面中啟動一個js邏輯通常是放在一個類似onDomReady的方法中,如果頁面中存在多個邏輯的時候怎么辦呢?比如,a實現了邏輯A,頁面代碼是這樣的
<script>
$.onDomReady(function(){
LogicA.start();
});
</script>
這段代碼通常寫在頁面的尾部,這段邏輯所伴隨的html代碼是埋藏在頁面的某處,這時b要在頁面中增加邏輯B,b的做法是首先找到尾部的這段代碼,然后更改成如下模樣:
<script src=”logicB.js” />
<script>
$.onDomReady(function(){
LogicA.start();
LogicB.start();
});
</script>
同樣,B邏輯所伴隨的html代碼也是埋藏在頁面的某處,這樣看來,js邏輯和其伴隨的html代碼如此分離,以至于到了刪減功能的時候,往往刪掉html卻忘了刪js,或者刪掉js忘了刪除html,在重用代碼的時候也會是個麻煩。同樣,在調試的時候,工程師也要打開兩個窗口分別關注js和html,即使這兩段代碼在同一個文件當中。如此則不如把代碼寫成這樣:
<script src=”logicA.js” />
<script>
$.onDomReady(function(){
LogicA.start();
});
</script>
<!–B邏輯的html代碼段–>
<script src=”logicB.js” />
<script>
$.onDomReady(function(){
LogicB.start();
});
</script>
這種coding寫法正是yui所提倡的,也就是所謂的沙箱,每個邏輯包含在一個沙箱中,各司其則互不干擾。當第三者瀏覽代碼的時候也立即明白這就是一個獨立的功能塊,包含典型的html結構和啟動邏輯的js,要重用,整塊拷走即可。
4、代碼污染 or 沙箱
剛才提到的沙箱可以解決一部分代碼污染的問題,新人閱讀代碼不用再看著亂碼般的源碼,“瞻前顧后”小心翼翼的尋找html和js的對應關系。但這種寫法也存在隱患,現在的前端開發越來越復雜也更多的使用分層,比如底層使用yui,中間層是自定義的工具庫,或者再加一個項目的widget組件庫,寫頁面邏輯則是基于這些庫進行開發。這樣的話,每段邏輯可能寫成這個樣子:
<script src=”widget.js” /><!–項目的widget庫–>
<script src=”logicA.js” />
<script>
$.onDomReady(function(){
LogicA.start();
});
</script>
盡管我們可以約定,將項目用到的所有的組件都統一加載進頁面中,但當組件越來越多的時候,就出現了上文所說的一步到位和顆粒化之間的矛盾,當每個控件單獨占用一個js文件和與之相伴隨的css皮膚,一個相對復雜的邏輯就變成了上文所說的手動引入js文件,并隨之引入一些顯而易見的弊端:

<script src=”calendar.js” /><!–日歷–>
<script src=”box.js” /><!–彈層–>
<script src=”tabview.js” /><!–幻燈原型–>
<script src=”slider.js” /><!–幻燈–>
<script src=”logicA.js” />
<script>
$.onDomReady(function(){
LogicA.start();
});
</script>
首先,手寫大量的js文件會各自占用單獨的http請求,在者,這個場景中,slider.js繼承自tabview.js,因此要著重關注他們的順序,第三,如果別人在頁面的其他地方也使用了日歷或者幻燈,還要再加兩個相同的js標簽?其實,說到這里,我們已經可以隱約看到大項目團隊開發的影子了,在一個迭代及其頻繁的項目中,開發者需要在短的時間內完成一個復雜頁面的某個功能的開發,而且不能影響到頁面的其他功能,畢竟,每添加一個功能,QA mm們都要將與之牽連的所有功能都要回歸,可辛苦了我們的QA mm們。在這種情況下,一個功能的開發可能和同一個頁面其他功能的開發相互并行。互相不屬于同一個項目組,也不知曉對方的實現。這種模式則是我們經常遇到的多人開發,沖突也大都由此產生,每個功能單獨看來是正確的,合并到一起會產生沖突和bug,這時調試bug則需要兩個工程師同時進行,很麻煩。甚者,當組件升級了,比如,tabview.js再繼承自tab.js,則又要去聯系各個工程師,將每個引用tabview.js的地方之前再加上一個tab.js,很麻煩。我們說,這種多人協作模式所帶來的沖突也是代碼污染的一種,因為每個人只能掌控類似tms區塊那么巴掌大的地方,所組成的最終頁面是什么樣,都不知道。更何況,這種生猛的引用js,也會阻塞頁面加載,占用http請求,影響性能。
鑒于此,yui將js的動態引入機制也并入其沙箱設計之中,我要引用的只是一個名字,比如slider.js,他依賴tabview.js,tabview.js依賴tab.js,這樣我只需引用一個名叫”slider”的東西即可,不用操心他依賴什么,更不用管如何引入到頁面中,yui都幫我們搞定:
TB.addmoudle({
logicA:{
fullpath:’logica.js’,
requires:['slider']
}
}).use(‘logicA’,function(Y){
LogicA.start();
});
</script>
當我們看最終組成的頁面的時候,看到的只是埋藏在頁面各個角落的這些簡短的結構一致的js代碼段。很易理解,這點代碼也不用像長的代碼塊壓成一行。