這篇文章主要介紹了詳解JavaScript中的客戶端消息框架設計原理,包括客戶端和服務器端的通信等方面的內容,需要的朋友可以參考下
哇——是個危險的題目,對嗎?我們對於什麼是本質的理解當然會隨著我們對要解決問題的理解而變化。因此我不會說謊——一年前我所理解的本質很不幸並不完整,因為我確信我將要寫的已經快伴隨我有6個月之久。所以,這篇文章是我在發現JavaScript中成功的運用客戶端消息模式的一些關鍵要點時的一個掠影。
1.) 理解中介者與觀察者的區別
大多數人在描述任何事件/消息機制的時候喜歡套用“發布者/訂閱者”(pub/sub)——但我認為這個術語不能很好的與抽象建立聯系。當然,從根本上說,一些東西訂閱了另一些東西發布的事件。但是發布者與訂閱者在何等層次上封裝在一起有可能使一個好的模式變得暗淡無光。那麼,區別在什麼地方呢?
觀察者
觀察者模式包括了被一個或多個觀察者所觀察的某個對象。典型的,該對象記錄下所有觀察者的痕跡,通常是用一個list來存儲觀察者注冊的回調方法,這些是觀察者為了接收通知而訂閱的。 注意: (哦,雙關語,我有多愛他們啊)(譯者注:Observe 觀察、注意)
?
1 2 3 4 5 6 7 var observer = { listen : function() { console.log("Yay for more cliché examples..."); } }; var elem = document.getElementById("cliche"); elem.addEventListener("click", observer.listen);一些需要注意的事情是:
我們必須獲得對此對象的直接引用
此對象必須保持一些內部的狀態,保存觀察者的回調痕跡
有時偵聽者不會利用由此對象返回的任何參數,理論上來說,有可能有 0-n*個參數 (更多是取決於以後會變得多有趣)
* n事實上不是無限的,但為了討論的目的,它指我們永遠也達不到的極限
中介者
中介者模式在一個對象與一個觀察者之間引入了一個“第三方”——有效的將二者解耦而且將他們之間如何通信封裝起來。一個中介者的API可能像“發布”、“訂閱”、“取消訂閱”一樣簡單,或者某個領域范圍內的實現可能被提供用來隱藏這些方法於某些更有意義的語義之中。大多數我用過的服務器端的實現更傾向於領域范圍而不是更簡單,但是並沒有對一個通用的中介者有任何規則限制!並不罕見,有種想法認為一個通用的中介者是一種信息經紀人。無論何種情形,結果都一樣——特定對象與觀察者之間不再互相直接知曉:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // It's fun to be naive! var mediator = { _subs: {}, // a real subscribe would at least check to make sure the // same callback instance wasn't registered 2x. // Sheesh, where did they find this guy?! subscribe: function(topic, callback) { this._subs[topic] = this._subs[topic] || []; this._subs[topic].push(callback); }, // lolwut? No ability to pass function context? :-) publish : function(topic, data) { var subs = this._subs[topic] || []; subs.forEach(function(cb) { cb(data); }); } } var FatherTime = function(med) { this.mediator = med; }; FatherTime.prototype.wakeyWakey = function() { this.mediator.publish("alarm.clock", { time: "06:00 AM", canSnooze: "heck-no-get-up-lazy-bum" }); } var Developer = function(mediator) { this.mediator = mediator; this.mediator.subscribe("alarm.clock", this.pleaseGodNo); }; Developer.prototype.pleaseGodNo = function(data) { alert("ZOMG, it's " + data.time + ". Please just make it stop."); } var fatherTime = new FatherTime(mediator); var developer = new Developer(mediator); fatherTime.wakeyWakey();你可能會想,除了特別純粹的中介者實現,特定對象不再負有保存訂閱者列表的責任,而且“時光老人”(FatherTime)與“開發者”(Developer)實例永遠沒法真正互相知道。他們只是共享了一個信息——將如我們今後所見,這是一個很重要的合約。 “很好,Jim。這對我而言仍然是發布者/訂閱者,那麼重點呢?我選擇某個方向真的會有區別嗎?”哦,繼續吧,親愛的讀者們,繼續吧。
2.) 了解什麼時候使用中介者和觀察者
使用本地的觀察者和中介者,即寫在組件當中的,而中介者看起來又像遠程的組件間通信。不管怎樣。我對待這種情況的原則雖然是——tl;dr(too long; don't read)(太長,不讀了)。但無論如何,反正串聯在一起最好。
要我簡捷地說真是麻煩,就像把幾個月來的細致體驗壓縮到裝不下140個字的溝裡。現實中回答這個問題肯定不簡潔。所以有一個長版本的解釋:
觀察者除了關心數據映射之外還有必要引用別的項目嗎?例如Backbone.View視圖有各種理由直接引用它的模型。這是非常自然的關系,視圖不僅要在模型改變時進行渲染,還需要調用模型的事件處理。如果段首的問題答案是”yes“,那觀察者就是有意義的。
如果觀察者和觀察對象的關系僅僅是依賴數據,那我願意使用中介pub/sub方式。兩個Backbone.View視圖或模型之間的通信,用觀察者是合適的。比如控制導航菜單的視圖發出的信息,是面包屑(breadcrumb)掛件需要的(響應當前的層級)。掛件不需要引用導航視圖,它只需要導航視圖提供信息。更關鍵的,導航視圖也許不是唯一的信息來源,別的視圖可能也可以提供。此時,中介pub/sub模式是最理想的——而且自身擴展性良好。
看起來這樣又好又全面,但是其實還有一個露點:如果我給對象定義一個本地事件,既想要觀察者直接調用,又可以被訂閱者間接訪問到,怎麼辦?這就是我為什麼說要串聯在一起:你推送或者橋接本地事件到消息組去吧。需要些更多代碼?很有可能——但是總比你把觀察對象傳遞給所有觀察者,一直緊耦合下去的情況好。然後,我們可以很好地繼續以下兩點...
3.) 選擇性的“提交”本地事件到總線
最開始我幾乎只用觀察者模式來在JavaScript中觸發事件。這是我們一次又一次遇到的模式,但更流行的客戶端輔助庫行為方式根本上來說是混合中介者的,給我們提供了就像它們是觀察者模式的API。我最初寫postal.js的時候,開始走進“為所有事物搭中介”的階段。在我寫的原型與構造函數中,分布各處的發布與訂閱的調用並不罕見。當我從這個改變中自然的解耦受益時,非基礎的代碼開始似乎充滿了相關於基礎的部分。構造函數到處都要帶上一個通道,訂閱被當作新實例的一部分被創建,原型方法直接發布一個數值到總線(甚至本地的訂閱者都不能直接的而必須監聽總線以獲得信息)。將這些明顯關於總線的東西納入app的這些部分,開始像是代碼的味道。代碼的“敘述”似乎總是被打斷,如“噢,將這個向所有訂閱者發布出去”,“等等!等等!監聽這個通道那個事情。好,現在繼續吧”。我的測試忽然開始需要依賴總線來做低層次的單元測試。而這感覺有點不對勁。
鐘擺擺動的指向了中間,我認識到我應該保持一個“本地API”,並且在需要的時候通過一個中介者為應用擴展其可以觸及的數據。 例如,我的backbone視圖與模型,仍然用普通的Backbome.Events行為來給本地觀察者發送事件(就是說,模型的事件被它相應的視圖所觀察)。當app的其它部分需要知道模型的變化時,我開始通過這些行將本地事件與總線橋接起來:
?
1 2 3 4 5 6 7 8 9 10 1