欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

大型JavaScript應(yīng)用程序架構(gòu)設(shè)計(jì)模式

 更新時(shí)間:2016年06月29日 11:02:40   投稿:yourber  
11月中旬在倫敦舉行的jQuery Summit頂級(jí)大會(huì)上有個(gè)session講的是大型JavaScript應(yīng)用程序架構(gòu),看完P(guān)PT以后覺(jué)得甚是不錯(cuò),于是整理一下發(fā)給大家共勉。

PDF版的PPT下載地址:http://www.slideshare.net/jibyjohnc/jqquerysummit-largescale-javascript-application-architecture

注:在整理的過(guò)程中,發(fā)現(xiàn)作者有些思想是返來(lái)復(fù)去地說(shuō),所以刪減了一部分,如果你的英文良好,請(qǐng)直接閱讀英文的PPT。

以下是本文的主要章節(jié):

1. 什么叫“JavaScript大型程序”?

2. 顧當(dāng)前的程序架構(gòu)

3. 長(zhǎng)遠(yuǎn)考慮

4. 頭腦風(fēng)暴

5. 建議的架構(gòu)

   5.1 設(shè)計(jì)模式

        5.1.1 模塊論

            5.1.1.1 綜述

            5.1.1.2 Module模式

            5.1.1.3 對(duì)象自面量

            5.1.1.4 CommonJS模塊

        5.1.2 Facade模式

        5.1.3 Mediator模式

    5.2 應(yīng)用到你的架構(gòu)

        5.2.1 Facade - 核心抽象

        5.2.2 Mediator - 程序核心

        5.2.3 緊密聯(lián)合運(yùn)作起來(lái)

6. 發(fā)布Pub/訂閱Sub的延伸:自動(dòng)注冊(cè)事件

7. Q & A

8. 致謝

什么叫“JavaScript大型程序”?

在我們開(kāi)始之前,我們來(lái)定義一下什么叫大型JavaScript站點(diǎn),很多有經(jīng)驗(yàn)的JS開(kāi)發(fā)高手也都被challenge住了,有人說(shuō)超過(guò)10萬(wàn)行JavaScript代碼才算大型,也有人說(shuō)JavaScript代碼要超過(guò)1MB大小才算,其實(shí)2者都不能算對(duì),因?yàn)椴荒馨惭b代碼量的多少來(lái)衡量,很多瑣碎的JS代碼很容易超過(guò)10萬(wàn)行的。

我對(duì)“大”的定義如下,雖然可能不太對(duì),但是應(yīng)該是比較接近了:

我個(gè)人認(rèn)為,大型JavaScript程序應(yīng)該是非常重要并且融入了很多卓越開(kāi)發(fā)人員努力,對(duì)重量級(jí)數(shù)據(jù)進(jìn)行處理并且展示給瀏覽器的程序。

回顧當(dāng)前的程序架構(gòu)

我不能強(qiáng)調(diào)說(shuō)這個(gè)問(wèn)題有多重要,很多有經(jīng)驗(yàn)的開(kāi)發(fā)人員經(jīng)常說(shuō):“現(xiàn)有的創(chuàng)意和設(shè)計(jì)模式在我上一個(gè)中型項(xiàng)目上運(yùn)行得非常好,所以在稍微大型點(diǎn)的程序里再次使用,應(yīng)該沒(méi)問(wèn)題,對(duì)吧?”,在一定程序上是沒(méi)錯(cuò)的,但別忘記了,既然是大型程序,通常就應(yīng)該有大的Concerns需要分解關(guān)注,我簡(jiǎn)短解釋一下要花時(shí)間來(lái)review當(dāng)前運(yùn)行了很久的程序架構(gòu)。大多數(shù)情況下,當(dāng)前的JavaScript程序架構(gòu)應(yīng)該是如下這個(gè)樣子的(注意,是JS架構(gòu),不是大家常說(shuō)的ASP.NET MVC):
    custom widgets
    models
    views
    controllers
    templates
    libraries/toolkits
    an application core.

你可能還會(huì)將程序單獨(dú)封裝成多個(gè)modules,或者使用其他的設(shè)計(jì)模式,這很好,但是如果這些結(jié)構(gòu)完全代表你的架構(gòu)的話,就可能會(huì)有一些潛在的問(wèn)題,我們來(lái)看看幾個(gè)重要的點(diǎn):

1.你架構(gòu)里的東西,有多少可以立即拿出來(lái)重用?
有沒(méi)有一些單獨(dú)的module不依賴別的代碼?是自包含么?如果我到你們正在使用的代碼庫(kù)上去隨即挑選一些模塊module代碼,然后放在一個(gè)新頁(yè)面,是否能立即就能使用?你可能會(huì)說(shuō)原理通就可以了,我建議你長(zhǎng)久打算一下,如果你的公司之前開(kāi)發(fā)很多重要的程序,突然有一天有人說(shuō),這個(gè)項(xiàng)目里的聊天模塊不錯(cuò),我們拿出來(lái)放在另外一個(gè)項(xiàng)目里吧,你能直接拿過(guò)來(lái)不修改代碼就能使用么?

2.系統(tǒng)里有多少模塊module需要依賴其他模塊?
系統(tǒng)的各個(gè)模塊是不是都很緊耦合?在我將這個(gè)問(wèn)題作為concern之前,我先解釋一下,不是說(shuō)所有的模塊都絕對(duì)不能有任何依賴,比如一個(gè)細(xì)粒度的功能可能是從base功能擴(kuò)展來(lái)的,我的問(wèn)題和這種情況不一樣,我說(shuō)的是不同功能模塊之前的依賴,理論上,所有的不同功能模塊都不應(yīng)該有太多的依賴。

3.如果你程序的某一部分出錯(cuò)了,其他部分是否能夠依然工作?
如果你構(gòu)建一個(gè)和Gmail差不多的程序,你可以發(fā)現(xiàn)Gmail里很多模塊都是動(dòng)態(tài)加載的,比如聊天chat模塊,在初始化頁(yè)面的時(shí)候是不加載的,而且就算加載以后出錯(cuò)了,頁(yè)面的其他部分也能正常使用。

4.你的各個(gè)模塊Module能很簡(jiǎn)單的進(jìn)行測(cè)試么?
你的每一個(gè)模塊都有可能用在數(shù)百萬(wàn)用戶的大型站點(diǎn)上,甚至多個(gè)站點(diǎn)都使用它,所以你的模塊需要能經(jīng)得住測(cè)試,也就是說(shuō),不管是在架構(gòu)內(nèi)部還是架構(gòu)外部,都應(yīng)該能很簡(jiǎn)單的去測(cè)試,包括大部分的斷言在不同的環(huán)境下都能夠通過(guò)。

長(zhǎng)遠(yuǎn)考慮

架構(gòu)大型程序的時(shí)候,最重要的是要有前瞻性,不能只考慮一個(gè)月或者一年以后的情況,要考慮更長(zhǎng)久的情況下,有什么改變的可能性?開(kāi)發(fā)人員經(jīng)常將DOM操作的代碼和程序綁定得太緊,盡管有時(shí)候已經(jīng)封裝單獨(dú)的邏輯到不同的模塊里了,想想一下,長(zhǎng)久以后,為什么不是很好。

我的一個(gè)同事曾經(jīng)說(shuō)過(guò),一個(gè)精確的架構(gòu)可能不適合未來(lái)的情景,有時(shí)候是正確的,但是當(dāng)你需要該做的話,你所付出的money那可是相當(dāng)?shù)囟嗯丁1热?,你可能因?yàn)槟承┬阅?,安全,設(shè)計(jì)的原因需要在Dojo, jQuery, Zepto, YUI之間需要選擇替換,這時(shí)候就有問(wèn)題了,大部分模塊都有依賴,需要錢呀,需要時(shí)間啊,需要人呀,對(duì)不?

對(duì)于一些小型站點(diǎn)沒(méi)事,但是大型站點(diǎn)確實(shí)需要提供一個(gè)更加靈活的機(jī)制,而不去擔(dān)心各個(gè)模塊之間的各種問(wèn)題,這既然節(jié)約錢,又能節(jié)約時(shí)間。

總結(jié)一下,現(xiàn)在你能確定你能不重寫整個(gè)程序就能替換一些類庫(kù)么?如果不能,那估計(jì)我們下面要講的內(nèi)容,就比較適合你了。

很多有經(jīng)驗(yàn)的JavaScript開(kāi)發(fā)者給出了一些關(guān)鍵的notes:

JavaScriptMVC的作者Justin Meyer說(shuō):

構(gòu)建大型程序最大的秘密就是從來(lái)不構(gòu)建大型程序,而是將程序分解成各個(gè)小的模塊去做,讓每個(gè)小模塊都可測(cè)試,可size化,然后集成到程序里。

High-performance JavaScript websites作者Nicholas,Zakas:
"The key is to acknowledge from the start that you have no idea how this will grow. When you accept that you don't know everything, you begin to design the system defensively. You identify the key areas that may change, which often is very easy when you put a little bit of time into it. For instance, you should expect that any part of the app that communicates with another system will likely change, so you need to abstract that away." -

一大堆文字問(wèn)題,太麻煩了,總結(jié)一句就是,一切皆可變,所以要抽象。

jQuery Fundamentals作者Rebecca Murphey:
各個(gè)模塊之間聯(lián)系的越密切,重用性越小,改變起來(lái)困難越大。

以上這些重要觀點(diǎn),是構(gòu)建架構(gòu)的核心要素,我們需要時(shí)刻銘記。

頭腦風(fēng)暴

我們來(lái)頭腦風(fēng)暴一下,我們需要一個(gè)松耦合的架構(gòu),各模塊之間沒(méi)有依賴,各個(gè)模塊和程序進(jìn)行通信,然后中間層接管和處理反饋相應(yīng)的消息。

例如,我們?nèi)绻幸粋€(gè)JavaScript構(gòu)建在線面包店程序,一個(gè)模塊發(fā)出了一個(gè)信息可能是“有42個(gè)圓面包需要派件”。我們使用不同的layer層來(lái)處理模塊發(fā)來(lái)的消息,做到如下:

    模塊不直接訪問(wèn)程序核心
    模塊不直接調(diào)用或影響其它的模塊

這將防止我們因?yàn)槟硞€(gè)模塊出錯(cuò),而導(dǎo)致所有的模塊出錯(cuò)。

另外一個(gè)問(wèn)題是安全,真實(shí)的情況是,大多數(shù)人都不認(rèn)為內(nèi)部安全是個(gè)問(wèn)題,我們自己心里說(shuō),程序是我自己構(gòu)建的,我知道哪些是公開(kāi)的那些私有的,安全沒(méi)問(wèn)題,但你有沒(méi)有辦法去定義哪個(gè)模塊才能權(quán)限訪問(wèn)程序核心?例如,有一個(gè)chat聊天模塊,我不想讓他調(diào)用admin模塊,或者不想讓它調(diào)用有DB寫權(quán)限的模塊,因?yàn)檫@之間存在很脆弱,很容易導(dǎo)致XSS攻擊。每個(gè)模塊不應(yīng)該能做所有的事情,但是當(dāng)前大多數(shù)架構(gòu)里的JavaScript代碼都有這種的問(wèn)題。提供一個(gè)中間層來(lái)控制,哪個(gè)模塊可以訪問(wèn)那個(gè)授權(quán)的部分,也就是說(shuō),該模塊最多只能做到我們所授權(quán)的那部分。

建議的架構(gòu)

我們本文的重點(diǎn)來(lái)了,這次我們提議的架構(gòu)使用了我們都很熟知的設(shè)計(jì)模式:module, facade和mediator。

和傳統(tǒng)的模型不一樣的是,為了解耦各個(gè)模塊,我們只讓模塊發(fā)布一些event事件,mediator模式可以負(fù)責(zé)從這些模塊上訂閱消息message,然后控制通知的response,facade模式用戶限制各模塊的權(quán)限。

以下是我們要注意講解的部分:
    1 設(shè)計(jì)模式
        1.1 模塊論
            1.1.1 綜述
            1.1.2 Module模式
            1.1.3 對(duì)象自面量
            1.1.4 CommonJS模塊
        1.2 Facade模式
        1.3 Mediator模式
    2 應(yīng)用到你的架構(gòu)
        2.1 Facade - 核心抽象
        2.2 Mediator - 程序核心
        2.3 緊密聯(lián)合運(yùn)作起來(lái)

模塊論

大家可能都或多或少地使用了模塊化的代碼,模塊是一個(gè)完整的強(qiáng)健程序架構(gòu)的一部分,每個(gè)模塊都是為了單獨(dú)的目的為創(chuàng)建的,回到Gmail,我們來(lái)個(gè)例子,chat聊天模塊看起來(lái)是個(gè)單獨(dú)的一部分,其實(shí)它是有很多單獨(dú)的子模塊來(lái)構(gòu)成,例如里面的表情模塊其實(shí)就是單獨(dú)的子模塊,也被用到了發(fā)送郵件的窗口上。

另外一個(gè)是模塊可以動(dòng)態(tài)加載,刪除和替換。

在JavaScript里,我們又幾種方式來(lái)實(shí)現(xiàn)模塊,大家熟知的是module模式和對(duì)象字面量,如果你已經(jīng)熟悉這些,請(qǐng)忽略此小節(jié),直接跳到CommonJS部分。

Module模式

module模式是一個(gè)比較流行的設(shè)計(jì)模式,它可以通過(guò)大括號(hào)封裝私有的變量,方法,狀態(tài)的,通過(guò)包裝這些內(nèi)容,一般全局的對(duì)象不能直接訪問(wèn),在這個(gè)設(shè)計(jì)模式里,只返回一個(gè)API,其它的內(nèi)容全部被封裝成私有的了。

另外,這個(gè)模式和自執(zhí)行的函數(shù)表達(dá)式比較相似,唯一的不同是module返回的是對(duì)象,而自執(zhí)行函數(shù)表達(dá)式返回的是function。

眾所周知, JavaScript不想其它語(yǔ)言一樣有訪問(wèn)修飾符,不能為每個(gè)字段或者方法聲明private,public修飾符,那這個(gè)模式我們是如何實(shí)現(xiàn)的呢?那就是return一個(gè)對(duì)象,里面包括一些公開(kāi)的方法,這些方法有能力去調(diào)用內(nèi)部的對(duì)象。

看一下,下面的代碼,這段代碼是一個(gè)自執(zhí)行代碼,聲明里包括了一個(gè)全局的對(duì)象basketModule, basket數(shù)組是一個(gè)私有的,所以你的整個(gè)程序是不能訪問(wèn)這個(gè)私有數(shù)組的,同時(shí)我們r(jià)eturn了一個(gè)對(duì)象,其內(nèi)包含了3個(gè)方法(例如addItem,getItemCount,getTotal),這3個(gè)方法可以訪問(wèn)私有的basket數(shù)組。

var basketModule = (function() {
var basket = []; //private
return { //exposed to public
  addItem: function(values) {
    basket.push(values);
  },
  getItemCount: function() {
    return basket.length;
  },
  getTotal: function(){
    var q = this.getItemCount(),p=0;
    while(q--){
    p+= basket[q].price;
    }
    return p;
  }
 }
}());

同時(shí)注意,我們r(jià)eturn的對(duì)象直接賦值給了basketModule,所以我們可以像下面一樣使用:

//basketModule is an object with properties which can also be methods
basketModule.addItem({item:'bread',price:0.5});
basketModule.addItem({item:'butter',price:0.3});
 
console.log(basketModule.getItemCount());
console.log(basketModule.getTotal());
 
//however, the following will not work:
console.log(basketModule.basket);// (undefined as not inside the returned object)
console.log(basket); //(only exists within the scope of the closure)

那在各個(gè)流行的類庫(kù)(如Dojo, jQuery)里是如何來(lái)做呢?

Dojo

Dojo試圖使用dojo.declare來(lái)提供class風(fēng)格的聲明方式,我們可以利用它來(lái)實(shí)現(xiàn)Module模式,例如如果你想再store命名空間下聲明basket對(duì)象,那么可以這么做:

//traditional way
var store = window.store || {};
store.basket = store.basket || {};
 
//using dojo.setObject
dojo.setObject("store.basket.object", (function() {
  var basket = [];
  function privateMethod() {
    console.log(basket);
  }
  return {
    publicMethod: function(){
      privateMethod();
    }
   };
}()));

結(jié)合dojo.provide一起來(lái)使用,非常強(qiáng)大。

YUI

下面的代碼是YUI原始的實(shí)現(xiàn)方式:

YAHOO.store.basket = function () {

 //"private" variables:
 var myPrivateVar = "I can be accessed only within YAHOO.store.basket .";

 //"private" method:
 var myPrivateMethod = function () {
 YAHOO.log("I can be accessed only from within YAHOO.store.basket");
 }

 return {
 myPublicProperty: "I'm a public property.",
 myPublicMethod: function () {
  YAHOO.log("I'm a public method.");

  //Within basket, I can access "private" vars and methods:
  YAHOO.log(myPrivateVar);
  YAHOO.log(myPrivateMethod());

  //The native scope of myPublicMethod is store so we can
  //access public members using "this":
  YAHOO.log(this.myPublicProperty);
 }
 };

} (); 

jQuery

jQuery里有很多Module模式的實(shí)現(xiàn),我們來(lái)看一個(gè)不同的例子,一個(gè)library函數(shù)聲明了一個(gè)新的library,然后創(chuàng)建該library的時(shí)候,在document.ready里自動(dòng)執(zhí)行init方法。

function library(module) {
  $(function() {
    if (module.init) {
      module.init();
    }
  });
  return module;
}
 
var myLibrary = library(function() {
  return {
    init: function() {
      /*implementation*/
      }
  };
}());

對(duì)象自面量

對(duì)象自面量使用大括號(hào)聲明,并且使用的時(shí)候不需要使用new關(guān)鍵字,如果對(duì)一個(gè)模塊里的屬性字段的publice/private不是很在意的話,可以使用這種方式,不過(guò)請(qǐng)注意這種方式和JSON的不同。對(duì)象自面量:var item={name: "tom", value:123} JSON:var item={"name":"tom", "value":123}。

var myModule = {
 myProperty: 'someValue',
 //object literals can contain properties and methods.
 //here, another object is defined for configuration
 //purposes:
 myConfig: {
 useCaching: true,
 language: 'en'
 },
 //a very basic method
 myMethod: function () {
 console.log('I can haz functionality?');
 },
 //output a value based on current configuration
 myMethod2: function () {
 console.log('Caching is:' + (this.myConfig.useCaching) ? 'enabled' : 'disabled');
 },
 //override the current configuration
 myMethod3: function (newConfig) {
 if (typeof newConfig == 'object') {
  this.myConfig = newConfig;
  console.log(this.myConfig.language);
 }
 }
};

 
myModule.myMethod(); //I can haz functionality
myModule.myMethod2(); //outputs enabled
myModule.myMethod3({ language: 'fr', useCaching: false }); //fr

CommonJS

關(guān)于 CommonJS的介紹,這里就不多說(shuō)了,之前很多文章都有介紹,我們這里要提一下的是CommonJS標(biāo)準(zhǔn)里里有2個(gè)重要的參數(shù)exports和require,exports是代表要加載的模塊,require是代表這些加載的模塊需要依賴其它的模塊,也需要將它加載進(jìn)來(lái)。

/*
Example of achieving compatibility with AMD and standard CommonJS by putting boilerplate around the standard CommonJS module format:
*/
 
(function(define){
  define(function(require,exports){
    // module contents
    var dep1 = require("dep1");
    exports.someExportedFunction = function(){...};
    //...
  });
})(typeof define=="function"?define:function(factory){factory(require,exports)});

有很多CommonJS標(biāo)準(zhǔn)的模塊加載實(shí)現(xiàn),我比較喜歡的是RequireJS,它能否非常好的加載模塊以及相關(guān)的依賴模塊,來(lái)一個(gè)簡(jiǎn)單的例子,例如需要將圖片轉(zhuǎn)化成ASCII碼,我們先加載encoder模塊,然后獲取他的encodeToASCII方法,理論上代碼應(yīng)該是如下:

var encodeToASCII = require("encoder").encodeToASCII;
exports.encodeSomeSource = function(){
  //其它操作以后,然后調(diào)用encodeToASCII
}

但是上述代碼并沒(méi)用工作,因?yàn)閑ncodeToASCII函數(shù)并沒(méi)用附加到window對(duì)象上,所以不能使用,改進(jìn)以后的代碼需要這樣才行:

define(function(require, exports, module) {
  var encodeToASCII = require("encoder").encodeToASCII;
    exports.encodeSomeSource = function(){
    //process then call encodeToASCII
  }
});

CommonJS 潛力很大,但是由于大叔不太熟,所以就不過(guò)多地介紹了。

Facade模式

Facade模式在本文架構(gòu)里占有重要角色,關(guān)于這個(gè)模式很多JavaScript類庫(kù)或者框架里都有體現(xiàn),其中最大的作用,就是包括High level的API,以此來(lái)隱藏具體的實(shí)現(xiàn),這就是說(shuō),我們只暴露接口,內(nèi)部的實(shí)現(xiàn)我們可以自己做主,也意味著內(nèi)部實(shí)現(xiàn)的代碼可以很容易的修改和更新,比如今天你是用jQuery來(lái)實(shí)現(xiàn)的,明天又想換YUI了,這就非常方便了。

下面這個(gè)例子了,可以看到我們提供了很多私有的方法,然后通過(guò)暴露一個(gè)簡(jiǎn)單的 API來(lái)讓外界執(zhí)行調(diào)用內(nèi)部的方法:

var module = (function () {
 var _private = {
 i: 5,
 get: function () {
  console.log('current value:' + this.i);
 },
 set: function (val) {
  this.i = val;
 },
 run: function () {
  console.log('running');
 },
 jump: function () {
  console.log('jumping');
 }
 };
 return {
 facade: function (args) {
  _private.set(args.val);
  _private.get();
  if (args.run) {
  _private.run();
  }
 }
 }
} ());

module.facade({run:true, val:10});
//outputs current value: 10, running

Facade和下面我們所說(shuō)的mediator的區(qū)別是,facade只提供現(xiàn)有存在的功能,而mediator可以增加新功能。

 Mediator模式

講modiator之前,我們先來(lái)舉個(gè)例子,機(jī)場(chǎng)飛行控制系統(tǒng),也就是傳說(shuō)中的塔臺(tái),具有絕對(duì)的權(quán)利,他可以控制任何一架飛機(jī)的起飛和降落時(shí)間以及地方,而飛機(jī)和飛機(jī)之前不允許通信,也就是說(shuō)塔臺(tái)是機(jī)場(chǎng)的核心,mediator就相當(dāng)于這個(gè)塔臺(tái)。

mediator就是用在程序里有多個(gè)模塊,而你又不想讓各個(gè)模塊有依賴的話,那通過(guò)mediator模式可以達(dá)到集中控制的目的。實(shí)際場(chǎng)景中也是,mediator封裝了很多不想干的模塊,讓他們通過(guò)mediator聯(lián)系在一起,同時(shí)也松耦合他們,使得他們之間必須通過(guò)mediator才能通信。

那mediator模式的優(yōu)點(diǎn)是什么?那就是解耦,如果你之前對(duì)觀察者模式比較了解的話,那理解下面的mediator圖就相對(duì)簡(jiǎn)單多了,下圖是一個(gè)high level的mediator模式圖:

http://img.jbzj.com/file_images/article/201606/201606291113241.jpg

想想一下,各模塊是發(fā)布者,mediator既是發(fā)布者又是訂閱者。

    Module 1向Mediator廣播一個(gè)實(shí)際,說(shuō)需要做某事
    Mediator捕獲消息以后,立即啟動(dòng)處理該消息需要使用的Module 2,Module 2處理結(jié)束以后返回信息給Mediator
    與此同時(shí),Mediator也啟動(dòng)了Module 3,當(dāng)接受Module 2 返回消息的時(shí)候自動(dòng)記錄日志到Module 3里

可以看到,各模塊之間并沒(méi)有通信,另外Mediator也可以實(shí)現(xiàn)監(jiān)控各模塊狀態(tài)的功能,例如如果Module 3出錯(cuò)了,Mediator可以暫時(shí)只想其它模塊,然后重啟Module 3,然后繼續(xù)執(zhí)行。

回顧一下,可以看到,Mediator的優(yōu)點(diǎn)是:松耦合的模塊由同一的Mediator來(lái)控制,模塊只需要廣播和監(jiān)聽(tīng)事件就可以了,而模塊之間不需要直接聯(lián)系,另外,一次信息的處理可以使用多個(gè)模塊,也方便我們以后統(tǒng)一的添加新的模塊到現(xiàn)有的控制邏輯里。

確定是:由于所有的模塊直接都不能直接通信,所有相對(duì)來(lái)說(shuō),性能方面可能會(huì)有少許下降,但是我認(rèn)為這是值得的。

我們根據(jù)上面的講解來(lái)一個(gè)簡(jiǎn)單的Demo:

var mediator = (function(){
 var subscribe = function(channel, fn){
 if (!mediator.channels[channel]) mediator.channels[channel] = [];
 mediator.channels[channel].push({ context: this, callback: fn });
 return this;
 },
 
 publish = function(channel){
 if (!mediator.channels[channel]) return false;
 var args = Array.prototype.slice.call(arguments, 1);
 for (var i = 0, l = mediator.channels[channel].length; i < l; i++) {
  var subscription = mediator.channels[channel][i];
  subscription.callback.apply(subscription.context, args);
 }
 return this;
 };
 
 return {
 channels: {},
 publish: publish,
 subscribe: subscribe,
 installTo: function(obj){
  obj.subscribe = subscribe;
  obj.publish = publish;
 }
 };
 
}());

然后有2個(gè)模塊分別調(diào)用:

//Pub/sub on a centralized mediator
 
mediator.name = "tim";
mediator.subscribe('nameChange', function(arg){
 console.log(this.name);
 this.name = arg;
 console.log(this.name);
});
 
mediator.publish('nameChange', 'david'); //tim, david
 
 
//Pub/sub via third party mediator
 
var obj = { name: 'sam' };
mediator.installTo(obj);
obj.subscribe('nameChange', function(arg){
 console.log(this.name);
 this.name = arg;
 console.log(this.name);
});
 
obj.publish('nameChange', 'john'); //sam, john

應(yīng)用Facade: 應(yīng)用程序核心的抽象

一個(gè)facade是作為應(yīng)用程序核心的一個(gè)抽象來(lái)工作的,在mediator和模塊之間負(fù)責(zé)通信,各個(gè)模塊只能通過(guò)這個(gè)facade來(lái)和程序核心進(jìn)行通信。作為抽象的職責(zé)是確保任何時(shí)候都能為這些模塊提供一個(gè)始終如一的接口(consistent interface),和sendbox controller的角色比較類似。所有的模塊組件通過(guò)它和mediator通信,所以facade需要是可靠的,可信賴的,同時(shí)作為為模塊提供接口的功能,facade還需要扮演另外一個(gè)角色,那就是安全控制,也就是決定程序的哪個(gè)部分可以被一個(gè)模塊訪問(wèn),模塊組件只能調(diào)用他們自己的方法,并且不能訪問(wèn)任何未授權(quán)的內(nèi)容。例如,一個(gè)模塊可能廣播dataValidationCompletedWriteToDB,這里的安全檢查需要確保該模塊擁有數(shù)據(jù)庫(kù)的寫權(quán)限。

總之,mediator只有在facade授權(quán)檢測(cè)以后才能進(jìn)行信息處理。

應(yīng)用Mediator:應(yīng)用程序的核心

Mediator是作為應(yīng)用程序核心的角色來(lái)工作的,我們簡(jiǎn)單地來(lái)說(shuō)一下他的職責(zé)。最核心的工作就是管理模塊的生命周期(lifecycle),當(dāng)這個(gè)核心撲捉到任何信息進(jìn)來(lái)的時(shí)候,他需要判斷程序如何來(lái)處理——也就是說(shuō)決定啟動(dòng)或停止哪一個(gè)或者一些模塊。當(dāng)一個(gè)模塊開(kāi)始啟動(dòng)的時(shí)候,它應(yīng)該能否自動(dòng)執(zhí)行,而不需要應(yīng)用程序核心來(lái)決定是否該執(zhí)行(比如,是否要在DOM ready的時(shí)候才能執(zhí)行),所以說(shuō)需要模塊自身需要去判定。

你可能還有問(wèn)題,就是一個(gè)模塊在什么情況下才會(huì)停止。當(dāng)程序探測(cè)到一個(gè)模塊失敗了,或者是出錯(cuò)了,程序需要做決定來(lái)防止繼續(xù)執(zhí)行該模塊里的方法,以便這個(gè)組件可以重新啟動(dòng),目的主要是提高用戶體驗(yàn)。

另外,該核心應(yīng)該可以動(dòng)態(tài)添加或者刪除模塊,而不影響其他任何功能。常見(jiàn)的例子是,一個(gè)模塊在頁(yè)面加載初期是不可用,但是用戶操作以后,需要?jiǎng)討B(tài)加載這個(gè)模塊然后執(zhí)行,就像Gmail里的chat聊天功能一樣,從性能優(yōu)化的目的來(lái)看,應(yīng)該是很好理解的吧。

異常錯(cuò)誤處理,也是由應(yīng)用程序核心來(lái)處理的,另外各模塊在廣播信息的時(shí)候,也廣播任何錯(cuò)誤到該核心里,以便程序核心可以根據(jù)情況去停止/重啟這些模塊。這也是松耦合架構(gòu)一個(gè)很重要的部分,我們不需要手工改變?nèi)魏文K,通過(guò)mediator使用發(fā)布/訂閱就可以來(lái)做到這個(gè)。

組裝起來(lái)

各模塊包含了程序里各種各樣的功能,他們有信息需要處理的時(shí)候,發(fā)布信息通知程序(這是他們的主要職責(zé)),下面的QA小節(jié)里提到了,模塊可以依賴一些DOM工具操作方法,但是不應(yīng)該和系統(tǒng)的其它模塊有依賴,一個(gè)模塊不應(yīng)該關(guān)注如下內(nèi)容:

    1.哪個(gè)對(duì)象或者模塊訂閱了這個(gè)模塊發(fā)布的信息
    2.這些對(duì)象是客戶端對(duì)象還是服務(wù)器端對(duì)象
    3.多少對(duì)象訂閱了你的信息

http://img.jbzj.com/file_images/article/201606/201606291113242.gif

Facade抽象應(yīng)用程序的核心,避免各個(gè)模塊之間直接通信,它從各模塊上訂閱信息,也負(fù)責(zé)授權(quán)檢測(cè),確保每個(gè)模塊有用自己?jiǎn)为?dú)的授權(quán)。

http://img.jbzj.com/file_images/article/201606/201606291113243.gif

Mediator(應(yīng)用程序核心)使用mediator模式扮演發(fā)布/訂閱管理器的角色,負(fù)責(zé)模塊管理以及啟動(dòng)/停止模塊執(zhí)行,可以動(dòng)態(tài)加載以及重啟有錯(cuò)誤的模塊。

http://img.jbzj.com/file_images/article/201606/201606291113244.gif

這個(gè)架構(gòu)的結(jié)果是:各模塊之間沒(méi)有依賴,因?yàn)樗神詈系膽?yīng)用,它們可以很容易地被測(cè)試和維護(hù),各模塊可以很容易地在其它項(xiàng)目里被重用,也可以在不影響程序的情況下動(dòng)態(tài)添加和刪除。

發(fā)布Pub/訂閱Sub的延伸:自動(dòng)注冊(cè)事件(Automatic Event Registration)

關(guān)于自動(dòng)注冊(cè)事件,需要遵守一定的命名規(guī)范,比如如果一個(gè)模塊發(fā)布了一個(gè)名字為messageUpdate的事件,那么所有帶有messageUpdate方法的模塊都會(huì)被自動(dòng)執(zhí)行。有好處也有利弊,具體實(shí)現(xiàn)方式,可以看我另外一篇帖子:jQuery自定義綁定的魔法升級(jí)版。

QA
1.有可能不使用facade或者類似的sandbox模式么?

盡管架構(gòu)的大綱里提出了facade可以實(shí)現(xiàn)授權(quán)檢查的功能,其實(shí)完全可能由mediator去做,輕型架構(gòu)要做的事情其實(shí)是幾乎一樣的,那就是解耦,確保各模塊直接和應(yīng)用程序核心通信是沒(méi)問(wèn)題的就行。

2.你提高了模塊直接不能有依賴,是否意味著不能依賴任何第三方類庫(kù)(例如jQuery)。

這其實(shí)就是一個(gè)兩面性的問(wèn)題,我們上面說(shuō)到了,一個(gè)模塊也許有一些子模塊,或者基礎(chǔ)模塊,比如基本的DOM操作工具類等,在這個(gè)層面上講,我們是可以用第三方類庫(kù)的,但是請(qǐng)確保,我們可以很容易地能否替換掉他們。

3.我喜歡這個(gè)架構(gòu),并且想開(kāi)始使用這個(gè)架構(gòu),有任何代碼樣本可以參考么?

我打算去搞一份代碼樣本供大家參考,不過(guò)在這之前,你可以參考Andrew Burgees的帖子Writing Modular JavaScript 。

4.如果模塊需要和應(yīng)用程序核心直接通信,是否可行?

技術(shù)上來(lái)將,沒(méi)有理由現(xiàn)在模塊不能和應(yīng)用程序核心直接通信,但是對(duì)于大多數(shù)應(yīng)用體驗(yàn)來(lái)說(shuō),還是不要。既然你選擇了這個(gè)架構(gòu),那就要遵守該架構(gòu)所定義的規(guī)則。

致謝

感謝Nicholas Zakas的原始貼,將思想總結(jié)在一起,感謝Andree Hansson的technical review,感謝Rebecca Murphey, Justin Meyer, John Hann, Peter Michaux, Paul Irish和Alex Sexton,他們所有的人都提供了和本Session相關(guān)的很多資料。

相關(guān)文章

最新評(píng)論