亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

詳解JavaScript中的客戶端消息框架設計原理

2019-11-20 12:10:21
字體:
來源:轉載
供稿:網友

 哇――是個危險的題目,對嗎?我們對于什么是本質的理解當然會隨著我們對要解決問題的理解而變化。因此我不會說謊――一年前我所理解的本質很不幸并不完整,因為我確信我將要寫的已經快伴隨我有6個月之久。所以,這篇文章是我在發現JavaScript中成功的運用客戶端消息模式的一些關鍵要點時的一個掠影。

1.) 理解中介者與觀察者的區別 
 大多數人在描述任何事件/消息機制的時候喜歡套用“發布者/訂閱者”(pub/sub)――但我認為這個術語不能很好的與抽象建立聯系。當然,從根本上說,一些東西訂閱了另一些東西發布的事件。但是發布者與訂閱者在何等層次上封裝在一起有可能使一個好的模式變得暗淡無光。那么,區別在什么地方呢?


觀察者

觀察者模式包括了被一個或多個觀察者所觀察的某個對象。典型的,該對象記錄下所有觀察者的痕跡,通常是用一個list來存儲觀察者注冊的回調方法,這些是觀察者為了接收通知而訂閱的。 注意: (哦,雙關語,我有多愛他們啊)(譯者注:Observe 觀察、注意)
 

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可能像“發布”、“訂閱”、“取消訂閱”一樣簡單,或者某個領域范圍內的實現可能被提供用來隱藏這些方法于某些更有意義的語義之中。大多數我用過的服務器端的實現更傾向于領域范圍而不是更簡單,但是并沒有對一個通用的中介者有任何規則限制!并不罕見,有種想法認為一個通用的中介者是一種信息經紀人。無論何種情形,結果都一樣――特定對象與觀察者之間不再互相直接知曉:
 

// 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個字的溝里?,F實中回答這個問題肯定不簡潔。所以有一個長版本的解釋:

    觀察者除了關心數據映射之外還有必要引用別的項目嗎?例如Backbone.View視圖有各種理由直接引用它的模型。這是非常自然的關系,視圖不僅要在模型改變時進行渲染,還需要調用模型的事件處理。如果段首的問題答案是”yes“,那觀察者就是有意義的。
    如果觀察者和觀察對象的關系僅僅是依賴數據,那我愿意使用中介pub/sub方式。兩個Backbone.View視圖或模型之間的通信,用觀察者是合適的。比如控制導航菜單的視圖發出的信息,是面包屑(breadcrumb)掛件需要的(響應當前的層級)。掛件不需要引用導航視圖,它只需要導航視圖提供信息。更關鍵的,導航視圖也許不是唯一的信息來源,別的視圖可能也可以提供。此時,中介pub/sub模式是最理想的――而且自身擴展性良好。

看起來這樣又好又全面,但是其實還有一個露點:如果我給對象定義一個本地事件,既想要觀察者直接調用,又可以被訂閱者間接訪問到,怎么辦?這就是我為什么說要串聯在一起:你推送或者橋接本地事件到消息組去吧。需要些更多代碼?很有可能――但是總比你把觀察對象傳遞給所有觀察者,一直緊耦合下去的情況好。然后,我們可以很好地繼續以下兩點...


3.) 選擇性的“提交”本地事件到總線

最開始我幾乎只用觀察者模式來在JavaScript中觸發事件。這是我們一次又一次遇到的模式,但更流行的客戶端輔助庫行為方式根本上來說是混合中介者的,給我們提供了就像它們是觀察者模式的API。我最初寫postal.js的時候,開始走進“為所有事物搭中介”的階段。在我寫的原型與構造函數中,分布各處的發布與訂閱的調用并不罕見。當我從這個改變中自然的解耦受益時,非基礎的代碼開始似乎充滿了相關于基礎的部分。構造函數到處都要帶上一個通道,訂閱被當作新實例的一部分被創建,原型方法直接發布一個數值到總線(甚至本地的訂閱者都不能直接的而必須監聽總線以獲得信息)。將這些明顯關于總線的東西納入app的這些部分,開始像是代碼的味道。代碼的“敘述”似乎總是被打斷,如“噢,將這個向所有訂閱者發布出去”,“等等!等等!監聽這個通道那個事情。好,現在繼續吧”。我的測試忽然開始需要依賴總線來做低層次的單元測試。而這感覺有點不對勁。

鐘擺擺動的指向了中間,我認識到我應該保持一個“本地API”,并且在需要的時候通過一個中介者為應用擴展其可以觸及的數據。 例如,我的backbone視圖與模型,仍然用普通的Backbome.Events行為來給本地觀察者發送事件(就是說,模型的事件被它相應的視圖所觀察)。當app的其它部分需要知道模型的變化時,我開始通過這些行將本地事件與總線橋接起來:
 

var SomeModel = Backbone.Model.extend({ initialize: function() {  this.on("change:superImportantField", function(model, value) {   postal.publish({    channel : "someChannel",    topic : "omg.super.important.field.changed",    data : {    muyImportante: value,    otherFoo: "otherBar"    }   });  }); }});

重要的是要認識到,當有可能透明的推送事件到消息總線時,本地事件和消息必須被認為是分開的合約――至少概念上如此。換句話說,你要能夠修改“內部的/本地的”事件而不破壞消息合約。這是要在腦海中記住的重要事實――否則你就是為緊耦合提供了一個新的途徑,在一個方法上走反了!

所以理所當然,上述的模型是可以在沒有消息總線的情況下被測試。而且如果我移去橋接在本地事件與總線之間的邏輯,我的視圖與模型依然工作得毫無不暢。但是,這可是七行的例子(盡管格式化了)。 僅僅橋接四個事件就需要幾乎三十行的代碼。

噢,你怎樣才能二者兼顧呢―― 在適合直接觀察者時本地通知,同時使涉及事件可以擴展,以便你的對象不必給所有對象都發送一圈――不需要代碼膨脹。通知怎樣才能很少的代碼又有更多的味道呢?

4.)在你的構架中隱藏樣板

這并不是說上面的例子中的代碼 ―― 將事件接入總線 ―― 的語法或概念是錯誤的(假設你接受本地和遠程/橋接事件的概念)。然而,這是一個很好的體現在代碼基礎之上培養良好習慣的作用的例子。有時我們會聽到類似“代碼實在太多了”的抱怨(特別是當 LOC 作為代碼質量的唯一判定者時)。 當這種情況下,我表示贊同。 它是一個可怕的樣板。  下面是我在橋接 Backbone 對象的本地事件到 postal.js 時使用的模式:
 

// the logic to wire up publications and subscriptions// exists in our custom MsgBackboneView constructorvar SomeView = MsgBackboneView.extend({  className : "i-am-classy",  // bridging local events triggered by this view publications: { // This is the more common 'shorthand' syntax // The key name is the name of the event. The // value is "channel topic" in postal. So this // means the bridgeTooFar event will get // published to postal on the "comm" channel // using a topic of "thats.far.enough". By default // the 1st argument passed to the event callback // will become the message payload. bridgeTooFar : "comm thats.far.enough",  // However, the longhand approach works like this: // The key is still the event name that will be bridged. // The value is an object that provides a channel name, // a topic (which can be a string or a function returning // a string), and an optional data function that returns // the object that should be the message payload. bridgeBurned: {  channel : "comm",  topic : "match.lit",  data : function() {   return { id: this.get("id"), foo: 'bar' };  } },  // This is how we subscribe to the bus and invoke // local methods to handle incoming messages subscriptions: {  // The key is the name of the method to invoke.  // The value is the "channel topic" to subscribe to.  // So this will subscribe to the "hotChannel" channel  // with a topic binding of "start.burning.*", and any  // message arriving gets routed to the "burnItWithFire"  // method on the view.  burnItWithFire : "hotChannel start.burning.*" },  burnItWithFire: function(data, envelope) {  // do stuff with message data and/or envelope }  // other wire-up, etc.});


顯然你可以用幾種不同的方式做這些――選擇總線式的框架――這要比樣板方式少很多無關內容,而且為Backbone開發人員所熟知。當你同時控制事件發送器和消息總線的實現時,橋接要更容易。這里有個將monologue.js發送器橋接到postal.js的例子: 
 

// using the 'monopost' add-on for monologue/postal:// assuming we have a worker instance that has monologue// methods on its prototype chain, etc. The keys are event// topic bindings to match local events to, and if a match is// found, it gets published to the channel specified in the// value (using the same topic value)worker.goPostal({ "match.stuff.like.#" : "ThisChannelYo", "secret.sauce.*" : "SeeecretChannel", "another.*.topic" : "YayMoarChannelsChannel"});

以不同的方式使用樣板是令人愉快的好習慣?,F在我可以分別獨立的測試我的本地對象,橋接代碼,甚至測試二者合一的生產&消費期待的消息過程等等。

同樣重要的是要注意到,如果我需要在上述的場景訪問普通的postal API,沒有什么可以阻止我這么做。沒有丟失靈活性這么就等于成功了


5.) 消息是合約――要明智的選擇實現方式

有兩種將數據傳遞給訂閱者的方法――也許可以給他們貼上更“官方”的標簽,我將如此描述他們:

  •     “0-n 參數”
  •     “封套” (或“單對象載荷“)

看看這些例子:
 

// 0-n argsthis.trigger("someGuyBlogged", "Jim", "Cowart", "JavaScript");// envelope stylethis.emit("someGuyBlogged", { firstName: "Jim", lastName: "Cowart", category: "JavaScript"});/* In an emitter like monologue.js, the emit call above would actually publish an envelope that looked similar to this: {  topic: "someGuyBlogged",  timeStamp: "2013-02-05T04:54:59.209Z",  data : {   firstName: "Jim",   lastName: "Cowart",   category: "JavaScript"  } }*/

經過一段時間,我發現封套方式比0-n參數方式要少很多很多麻煩(與代碼)。"0-n參數"途徑的挑戰主要在于兩個原因(就我的經驗而言):第一,很典型的是“當事件觸發時,你還記得要傳遞哪一個參數嗎?不記得?好,我想我會看看觸發的源頭”。不是一個真正意義上的好方法,對嗎?但它可以打斷代碼的正常流程。你可以用一個調試工具,檢測執行條件下的參數值并由此推斷基于這些數值的”標簽“,但哪個更簡單呢――看到一個”1.21“的參數值,困惑于它的意義,或者檢測一個對象并發現{千兆瓦:1.21}。第二個原因是由于伴隨事件傳送可選的數據,以及當方法簽名變得更長帶來的痛苦。


"說實話,Jim,你這是在搭車棚。"或許是的,但是一段時間以來我一直看到代碼的基礎在擴充與變形,簡單的包含一兩個參數的原始事件,在其間包含了可選的參數以后開始變得畸形:
 

// 最開始是這樣的this.trigger("someEvent", "a string!", 99);// 有一天, 它變得包含了一切this.trigger("someEvent", "string", 99, { sky: "blue" }, [1,2,3,4], true, 0);// 可是等等――第4和第5個參數是可選的,因此也可能傳的是:this.trigger("someEvent", "string", 99, [1,2,3,4], true, 0);// 噢,你還檢查第5個參數的真/假嗎?// 哎呦!現在是早先的參數了……this.trigger("someEvent", "string", 99, true, 0);

如果有任何數據是可選的,將沒有圍繞它的測試。但需要更少的代碼,需要能更具擴展性,特別典型的是能自解釋(感謝這些成員名字)以便能在逐一傳送給訂閱者回調方法時,對一個對象進行那種測試。我仍然在不得不用"0-n參數"的地方用它,但如果由我決定,將是一直用封套的方法――我的事件發送者和消息總線都是這樣。(說明我存在偏見,monologue與postal共享同一個封套的數據結構,去掉了monologue不用的通道)

因此――得承認用來給訂閱者傳輸數據的結構是”合約“的一個部分。在封套方式這個方向,你可以用額外的元數據描述事件(不需要增加額外的參數)――這保持了方法簽名(這就是合約的一個部分)對每個事件和訂閱者一致。你也能很容易的為一個信息結構編制版本(或在必要的時候增加其他封套層級的信息)。如果你沿著這個方向做的話,請確保用的是一致的封套結構。


6.) 消息”拓撲“比你想的還重要

這里沒有銀彈。但是你要對如何命名主題與通道,以及如何設計消息載荷的結構深思熟慮。我傾向于用兩種方法之一映射我的模型:用一個單一的數據通道,主題的前綴采用模型的名字,后跟其唯一的id,然后通過它的操作({modelType.id.operation})處理,或者給模型的自身通道,主題就是{id.operation}。一個恒定的習慣是在模型請求數據的時候自動響應這個行為。但并不是所有總線上的操作都是請求。可能有簡單的事件發布到app。你是否命名主題來描述事件(理想條件下)?或者你是否掉進了這樣的陷阱,通過命名主題來描述某個訂閱者可能的傾向行為?例如,包含“route.changed” 抑或 “show.customer.ui”主題的消息。一個表明了事件,另一個表明了命令。做這些決定的時候要仔細思考。命令并不壞,但在你需要請求/響應或命令之前,你會為事件所能描述的數量而吃驚的。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩欧美在线免费观看| 精品av在线播放| 亚洲色图日韩av| 精品久久久久久久久久| 中文字幕欧美日韩在线| 亚洲欧美精品伊人久久| 91福利视频在线观看| 一本色道久久88综合日韩精品| 国模视频一区二区三区| 日韩av影视在线| 欧美最顶级丰满的aⅴ艳星| 久久久视频在线| 日韩电影在线观看免费| 日韩一区二区三区xxxx| 精品动漫一区二区| 欧美黑人巨大精品一区二区| 欧美性xxxxxxx| 亚洲国产日韩欧美在线图片| 欧美国产日韩精品| 中文字幕在线看视频国产欧美| 国产91在线视频| 欧美日韩激情小视频| 精品成人国产在线观看男人呻吟| 欧美国产日韩一区| 国产一区二区三区四区福利| 成人国产精品久久久久久亚洲| 国模私拍一区二区三区| 欧美极品第一页| 亚洲精品456在线播放狼人| 精品国产成人在线| 91精品视频网站| 欧美性色xo影院| 在线日韩欧美视频| 久久久国产精品x99av| 亚洲va久久久噜噜噜久久天堂| 日韩专区在线观看| 国产精品扒开腿爽爽爽视频| 欧洲中文字幕国产精品| 亚洲性线免费观看视频成熟| 国产噜噜噜噜久久久久久久久| 成人免费视频97| 欧美激情视频网| 在线激情影院一区| 久久精品99久久香蕉国产色戒| 久久久精品国产| 91精品视频网站| 国产成人jvid在线播放| 久久精品这里热有精品| 欧美高清在线播放| 日韩激情视频在线播放| 成人观看高清在线观看免费| 国产成人精品国内自产拍免费看| 在线视频国产日韩| 国产精品欧美在线| 欧美午夜精品久久久久久浪潮| 久久免费福利视频| 亚洲精品一区av在线播放| 久久亚洲国产精品成人av秋霞| 欧美日韩精品在线观看| 精品久久久久久中文字幕大豆网| 91精品国产乱码久久久久久久久| 亚洲视频网站在线观看| 欧美激情久久久久| 好吊成人免视频| 精品magnet| 国产精品丝袜一区二区三区| 日韩欧美视频一区二区三区| 在线成人中文字幕| 亚洲国产一区二区三区四区| 日韩精品在线私人| 欧美在线视频免费播放| 日韩经典第一页| 在线看福利67194| 美女撒尿一区二区三区| 国产在线一区二区三区| 国产精品免费一区二区三区都可以| 青青a在线精品免费观看| 精品亚洲国产成av人片传媒| 91高清在线免费观看| 亚洲最大的av网站| 国产精品九九久久久久久久| 国产精品久久视频| 国自产精品手机在线观看视频| 色先锋久久影院av| 欧美老少做受xxxx高潮| 中国人与牲禽动交精品| 国产精品免费久久久久久| 色综合久综合久久综合久鬼88| 日韩精品视频观看| 久久精品中文字幕一区| 日韩精品视频免费专区在线播放| 国产精品久久久久久久久久久不卡| 久久久久久69| 中文字幕欧美亚洲| 国产男人精品视频| 91国产高清在线| www.美女亚洲精品| 中文字幕久久精品| 亚洲字幕在线观看| 国产一区二区三区在线观看网站| 色综合91久久精品中文字幕| 国外成人免费在线播放| 久久的精品视频| 欧美日本黄视频| 国产精品久久久久久久app| 日韩电影在线观看中文字幕| 亚洲国产成人爱av在线播放| 欧美成人午夜激情视频| 精品久久久久久电影| 国产精品久久久久久久久久久新郎| 91久久嫩草影院一区二区| 亚洲成人激情小说| 亚洲性无码av在线| 亚洲成人教育av| 91香蕉国产在线观看| 91九色综合久久| 精品成人av一区| 国产午夜精品理论片a级探花| 亚洲激情视频在线播放| 欧美日韩国产在线播放| 欧美成人免费va影院高清| 日本视频久久久| 国产亚洲欧美日韩精品| 国产精品丝袜久久久久久高清| 国产欧美日韩高清| 久久精品国产亚洲| 亚洲精品天天看| 精品无人区乱码1区2区3区在线| 欧美成人激情视频| 亚洲综合在线播放| 欧美亚洲在线播放| 欧美成人h版在线观看| 欧美黑人视频一区| 亚洲自拍偷拍福利| 狠狠躁18三区二区一区| 欧美视频一区二区三区…| 欧美日韩亚洲一区二区| 久久精品成人动漫| 日韩中文字幕av| 成人妇女免费播放久久久| 国产精品自拍偷拍视频| 色偷偷91综合久久噜噜| 国内精品视频久久| 久久久久久久久久久国产| 亚洲精品日韩av| 国模精品视频一区二区| 欧美激情亚洲一区| 久久久精品2019中文字幕神马| 蜜月aⅴ免费一区二区三区| 久久精品成人动漫| 亚洲精品免费一区二区三区| 亚洲人成在线免费观看| 色噜噜狠狠色综合网图区| 欧美日韩一区二区在线| 亚洲mm色国产网站| 亚洲国产一区自拍| 欧美成人精品三级在线观看| 高清一区二区三区四区五区| 中文字幕成人精品久久不卡| 国产女人精品视频| 精品夜色国产国偷在线| 亚洲伊人久久综合| 亚洲电影免费观看高清| 日韩在线视频免费观看高清中文|