這兩天在讀kissy的源代碼,從一開始我就對它的mix()函數充滿了敵意。因為無論從哪個角度來看,那都是一個極其低效的實作。不過深入了解這個架構之後,我對kissy中的新的系統建構的模型産生了興趣,而這種系統建構的方式,也正是由mix()所帶來的。
一、對象系統
我們先了解一下對象系統。在《JavaScript語言精髓與程式設計實踐》中談到過,面向對象系統有三種對象的繼承方式,即原型、類和元類。這三種方式都可以建構大型對象系統。在後續讨論之前,我們在名詞概念上做一些強調,所謂“對象系統”,是指由“一組對象構成的系統”,這些對象之間存在或不存在某種聯系,但通過一些規則組織起來。所謂“面向對象系統”,是指以上述“對象系統”對基礎延伸演化的系統,新系統滿足前對象系統的組織規則。
所謂對象系統的三個要素,即繼承、封裝與多态,即是上述組織規則的要件。孟岩同學從C/C++出發,從另一個側面談論對象系統,所持的觀點我相當認可,這包括所述的“對象範式的基本觀念中不包括繼承、封裝與多态”——這一觀點有其确切的背景與思考方法,值得一談。
我們在這裡要讨論的是“對象系統”,即,對象是如何組織起來的問題。在這個問題上,組織規則之一,就是“繼承”。JavaScript中基本的繼承模型是原型繼承,其特點是“新對象執行個體的特性,複制自一個原型對象執行個體”。Qomo以及其它的一些項目,通過語言擴充的方式,在JavaScript上添加了類繼承的模型,其特點是“對象建構自類,類是其父類的一個派生”,這裡的“派生”與“特性複制”有潛在的關系,即:子類的特性也複制自父類。正是因為“派生”其實是特性複制的一種形式,是以事實上Qomo中的類繼承,是通過原型繼承來實作的,因為原型繼承本質上也就是“特性複制”。
無論是原型繼承、類繼承還是這裡沒有進一步讨論的元類繼承,繼承的最終目的是建構一個“對象系統”,而不是“系統”。這一個小小的措辭上的差別,有着本質上的、深刻的意義,這也是我提及到孟岩的上一篇文章的原因。通常,由“繼承”入手了解的“對象系統”其實是靜态的,以至于我們面向對象系統開發的最後一步,仍然需要架構來驅動之。例如TApp.Run(),或者類似于new App(),等等。繼承所帶來的,主要仍然是指對象系統的組織性,而非其運作過程中的動态特性。于是我們通過更多類或其它對象系統,來将一個系統的動态特性靜态化,例如将對象之間的互動關系抽取出來,變成控制類。我們做這些事情的目的,僅僅是因為我們約定了對象系統的組織規則,要面向這個對象系統開發,也必然滿足(或契合)這一組織規則。組織規則限定了我們建構系統的方式——繼承、封裝與多态,這在一定程度上說是“對象系統建構”的一個方案,并非“系統建構”的方案。而孟岩在上文中讨論的,也正是“系統建構”的問題。是以孟岩提出兩點:
•程式是由對象組成的;
•對象之間互相發送消息,協作完成任務。
其第一條,是對象系統的基本特性,是謂系統成員;第二條,是對象系統如何演進為系統的特性,是謂系統通訊。一個系統的限制,既包括其成員(以及成員的組織規則),也包括成員間的通訊。
二、用mix()來建構系統
舍棄“繼承”這種方式不談,系統建構還有其它的什麼方法嗎?
kissy提供了另外一種可能性,即mix(),混合。在kissy系統的核心部分,為一個系統提出了三個概念:
1、原子(meta),一個系統具有至少一個原子,原子是具有mix()能力的一個對象;
2、宿主(host),一個系統有一個依賴的宿主,表明系統的外部環境,系統隻是其宿主環境中的部分内容,可以由特定的名稱來差別于其它;
3、種子(seed),一個系統誕生自一個種子,種子描述系統上述的meta和host兩個方面的特性。
kissy約定,一個系統誕生自一個種子,該種子通過不停地mix()而成長,變成一個複雜的系統。由種子培育成為系統的整個環境,隻需要能夠了解mix與host,即可以基于seed來建構任意複雜的系統。
上述的邏輯在kissy.js中,描述得相當簡單:
[javascript] view plaincopy
- (function(host, S) {
- var meta = {
- mix: function(r, s, ov, wl) {
- ...
- }
- },
- // If KISSY is already defined, the existing KISSY object will not
- // be overwritten so that defined namespaces are preserved.
- seed = (host && host[S]) || {},
- // The host of runtime environment. specify by user's seed or <this>,
- // compatibled for '<this> is null' in unknown engine.
- host = seed.__HOST || (seed.__HOST = host || {});
- // shortcut and meta for seed.
- S = host[S] = meta.mix(seed, meta, false);
- return S;
- })(this, 'KISSY');
這個系統初始化的時候,傳入host與host中的系統名S。對于kissy來說,host是目前的系統環境,這裡的this值,可以是javascript引擎的global,或浏覽器環境的window,或某個函數或對象閉包内的目前this。而'KISSY'值,表現kissy系統在環境中的名字。按照javascript的語言約定,我們可以通過host[S]來找到既已經存在的kissy系統。
按照此前的約定,一個mix建構的系統,必然有host和mix兩個性質,因為它最原始的種子(seed)就必然包括這兩種性質。是以,既然我們上面是通過host[S]來通路一個“既已存在的kissy系統”,則無論該kissy系統經過了何種程度的演化,必然會包括這兩種性質。
上面的建構過程嘗試尋找在host[S]中尋找這兩種性質,如果其中之一不存在,則嘗試初始化它。例如代碼:
- seed = (host && host[S]) || {},
如果host[S]是存的,則假設它是一個seed,否則初始化為一個空的對象。接下來:
- host = seed.__HOST || (seed.__HOST = host || {})
如果上述的種子seed有host屬性,則使用它既有的__HOST,如果沒有,則置為目前環境下的host,或一個空的對象。
現在我們看到的seed,必然已經具有host屬性。但是,它還“可能”缺少一個性質,即最最重要的mix()。mix()的作用其實很簡單,就是從對象B将屬性抄寫到對象A的一個方法。而這裡,之是以說是“可能”缺少,是因為如果seed是既有的mix系統,則他已經有mix()屬性;如果它是第三方系統,則可能沒有mix,或有一個不同的mix等等。下面的一行代碼嘗試用元語言的思想建構它,即:
- meta.mix(seed, meta, false); // false值表明不覆寫
元語言的特點是自描述的,meta.mix()可以向seed混入mix(),也可以使seed.mix()能混入其它系統或meta本身。總之在mix()的建構中,meta隻需要有mix這個方法,不需要更多,也不能更少。
上一行代碼的結果,是:如果seed沒有自已的mix()屬性,則向seed混入meta的原始的mix()。
現在,我們再看seed,必然已經具有了host和mix()屬性。它本身可能是一個空對象,也可能是一個龐大的既有系統,但無論如此,它具有了這兩個性質,就可以作為seed進一步的衍生。
在這一切之前,下面的代碼保證它位于HOST[S],并傳回這個系統:
- S = host[S] = meta.mix(seed, meta, false);
- return S;
三、mix()系統建構中的其它概念
kissy除了實作基本的mix系統之外,在core部分加入了其它的一些功能。包括除mix()之外的兩種混入方法:
- augment,擴充。用mix方法,将另一些子系統s[i]的原型,混入目标子系統r的原型。
- merge,合并。用mix方法,将另一些子系統s[i],混入目前子系統S。
基本上來說,augment是通過mix來對javascript的原型系統進行擴充的方法,或是在應用系統中,結合原型機制與混入機制來建構系統。而merge隻是mix方法的一個批量工具。
另外,考慮到面向對象系統中的繼承特性,kissy也實作了extend(派生)方法,以提供傳統的面向對象程式設計能力。
除了語言級别的概念之外,kissy也提供系統架構級别的一些建構能力。包括:
- app,應用。與host[S]并列的,具有同等能力的其它應用,app('XXX', ...)可以在host['XXX']上組織應用。
- namespace,命名空間。即可以組織出host[XXX].YYY.ZZZ這樣的,在不同子系統中的,不同命名空間下的系統。
最後,kissy在核心中也提供簡單的調試支援。
顯然的,基于mix的原則,任何一個第三方的系統可以通過混入kissy來修改上述的概念,例如覆寫extend()來實作自己的對象系統建構原則,或覆寫app()來實作自己的應用組織原則。第三方系統也可以将kissy混入自身,在保障自身特性的情況下,使用kissy,以及更大規模的kissy ui系統帶來的好處。
四、一點點提示
kISSY是什麼?
KISSY是一個開源的javascript項目,其主體是一個前端UI開發架構,即KissyUI。本文所述的kissy是僅指其核心部分的kissy.js中的語言與架構設計思想。KISSY項目的開源網站是:http://kissyteam.github.com/
kissy怎麼使用呢?
盡管在KissyUI向kissy核心化的過程中,我們提出了一些新的概念與架構模型,但事實上,我們并未改變KissyUI的任何使用慣例。從代碼上來看,kissy.js和lang.js以後的其它子產品,并沒有任何的變化,是以如果僅是将kissy當成一個UI系統來使用,你可以參考上面的開源網站,其中既有的KissyUI文檔是完全有效的,而且KissyUI本身也是一個優秀的、便捷的Web UI架構。但是,kissy系統在模向合并群組織上的能力大大增強了。例如說,我們可以開始想象下面這樣的代碼:
[xhtml] view plaincopy
- <!-- 先裝載jQuery -->
- <script src="jQuery.js" mce_src="jQuery.js"></script>
- <!-- 将jQuery系統映射到KISSY,作為初始的seed -->
- <script type="text/javascript">
- window.KISSY = jQuery;
- </script>
- <!-- 裝載kissy,在jQuery上混入KISSY -->
- <script src="kissy.js"></script>
- document.writeln('你現在使用的是Kissy,還是jQuyer?答案請選Y,或者Y');
- document.writeln('你現在能裝載Kissy UI,還是jQuery UI?答案請選Y,或者Y');
- KISSY.merge(YUI, Dojo, Qomo).merge(Biby);
- document.writeln("what's KISSY? select collapsar or black hole, pls