摘要:想繼續(xù)了解設(shè)計模式必須要先搞懂面向?qū)ο缶幊蹋駝t只會讓你自己更痛苦。創(chuàng)建型設(shè)計模式主要有簡單工廠模式,工廠方法模式,抽象工廠模式,建造者模式,原型模式和單例模式,下面一一道來。而工廠方法模式本意是將實際創(chuàng)建對象的工作推遲到子類中。
接觸前端兩三個月的時候,那時候只是聽說設(shè)計模式很重要,然后我就去讀了一本設(shè)計模式的書,讀了一部分,也不知道這些設(shè)計模式到底設(shè)計出來干嘛的,然后就沒再看了。后來就自己做一些小項目也覺著好像不需要用到設(shè)計模式這個東西呀?,F(xiàn)在,接觸前端有半年了,決定再重新看看設(shè)計模式,說不定會有一些啟發(fā)。于是發(fā)現(xiàn)了一本好書——《JavaScript設(shè)計模式》,寫的通俗易懂,用一個個故事串起了一整本書,看了一部分發(fā)現(xiàn)原來我平時寫代碼的時候無意之中就用到了一些設(shè)計模式,然后就忍不住都看完了。看完整本書,讓我完全改變了以前對設(shè)計模式的看法,也學(xué)到了很多在實際項目開發(fā)中的經(jīng)驗。這里就簡單總結(jié)下這本書,也算是做個筆記,供自己以后參考。(定義一般都比較晦澀難懂,可以先看看使用場景再回來理解相關(guān)定義)
先給個書的鏈接: JavaScript設(shè)計模式-張容銘
設(shè)計模式是代碼設(shè)計經(jīng)驗的總結(jié),為了可重用代碼,保證代碼的可靠性等。設(shè)計模式主要分為三大類型,創(chuàng)建型模式,結(jié)構(gòu)型模式和行為型模式,本書還額外寫了另兩類設(shè)計模式,技巧型模式和架構(gòu)型模式。JavaScript設(shè)計模式是以面向?qū)ο缶幊虨榛A(chǔ)的,JavaScript的面向?qū)ο缶幊毯蛡鹘y(tǒng)的C++、Java的面向?qū)ο缶幊逃行┎顒e,這讓我一開始接觸JavaScript的時候感到十分痛苦,但是這只能靠自己慢慢積累慢慢思考。想繼續(xù)了解JavaScript設(shè)計模式必須要先搞懂JavaScript面向?qū)ο缶幊蹋駝t只會讓你自己更痛苦。
創(chuàng)建型設(shè)計模式創(chuàng)建型設(shè)計模式是一類處理對象創(chuàng)建的設(shè)計模式,通過某種方式控制對象的創(chuàng)建來避免基本對象創(chuàng)建時可能導(dǎo)致設(shè)計上的問題或增加設(shè)計上的復(fù)雜度。創(chuàng)建型設(shè)計模式主要有簡單工廠模式,工廠方法模式,抽象工廠模式,建造者模式,原型模式和單例模式,下面一一道來。
簡單工廠模式作者把簡單工廠模式比喻成一個神奇的魔術(shù)師。
定義又叫靜態(tài)工廠方法,由一個工廠對象決定創(chuàng)建某一種產(chǎn)品對象類的實例,主要用來創(chuàng)建同一類對象。
使用場景看完上面的定義一定很不解,說的到底是啥,現(xiàn)在就舉個例子來解釋一下。比如體育商品店賣體育器材,里面有很多體育用品及其相關(guān)介紹。當(dāng)你來到體育用品店買一個籃球,只需問售貨員,他就會幫你找到你所要的東西。用程序?qū)崿F(xiàn)如下:
// 籃球基類 var Basketball = function() { this.intro = "籃球盛行于美國"; }; Basketball.prototype = { getMember: function() { console.log("每個隊伍需要5名隊員"); }, getBallSize: function() { console.log("籃球很大"); } }; // 足球基類 var Football = function() { this.intro = "足球盛行于美國"; }; Football.prototype = { getMember: function() { console.log("每個隊伍需要11名隊員"); }, getBallSize: function() { console.log("籃球很大"); } }; // 運動工廠 var SportsFactory = function(name) { switch(name) { case "NBA": return new Basketball(); case "wordCup": return new Football(); } };
當(dāng)你使用這個運動工廠時只需要記住SportsFactory這個工廠對象就好了,它會幫你找到你想要的。
簡單工廠模式的理念是創(chuàng)建對象,上面例子是將不同的類實例化,但是簡單工廠模式還可以創(chuàng)建相似對象,將相似的東西提取,不相似的針對性處理即可。這樣只需創(chuàng)建一個對象就可以替代多個類了。
團隊開發(fā)不同于個人,對全局變量的限制很大,要盡量少得創(chuàng)建全局變量。如果有同一類對象在不同需求中重復(fù)使用,那么大部分是不需要重復(fù)創(chuàng)建的,要學(xué)會代碼復(fù)用。用簡單工廠來創(chuàng)建對象,可以減少全局變量創(chuàng)建提高代碼復(fù)用率,它的使用場合限制在創(chuàng)建單一對象。
工廠方法模式作者把工廠方法模式比喻成一張名片。
定義通過對產(chǎn)品類的抽象使其創(chuàng)建業(yè)務(wù)主要負責(zé)用于創(chuàng)建多類產(chǎn)品的實例。
使用場景在實際開發(fā)中,需求的變更是很正常的,開始需求簡單可以直接創(chuàng)建對象,類似的需求多了可以用簡單工廠方法重構(gòu),但是如果需求不停變化,那么不僅要修改工廠函數(shù)還要添加類,這樣就沒完了。而工廠方法模式本意是將實際創(chuàng)建對象的工作推遲到子類中。
// 工廠類 var Factory = function(type, content) { if(this instanceof Factory) { var s = new this[type](content); return s; } else { // 防止使用者不知道這是一個類,忘了加new操作符創(chuàng)建,導(dǎo)致全局變量污染 return new Factory(type, content); } }; Factory.prototype = { Java: function(content) { // ... }, JavaScript: function(content) { // ... }, php: function(content) { // ... } };
這樣以后如果想添加其他類,只需要在Factory的原型里添加就可以了。
收獲與總結(jié)對于創(chuàng)建很多類的對象,簡單工廠模式就不適合了,通過工廠模式可以輕松創(chuàng)建多個類的實例對象,而且避免了使用者與對象類之間的耦合,用戶不必關(guān)心創(chuàng)建該對象的具體類,只需調(diào)用工廠方法即可。
抽象工廠模式抽象工廠模式讓你感覺出現(xiàn)的都是幻覺。
定義通過對類的工廠抽象使其業(yè)務(wù)用于對產(chǎn)品類簇的創(chuàng)建,而不負責(zé)某一類產(chǎn)品的實例。
抽象類抽象類是一種聲明但不能使用的類,當(dāng)你使用的時候就會報錯。JavaScript中的抽象類不能像傳統(tǒng)面向?qū)ο笳Z言那樣輕松地創(chuàng)建,我們可以在類的方法中手動拋出錯誤來模擬抽象類。你可能會想,這樣的類什么都不能做能有什么用?其實它在繼承上是很有用的。
使用場景抽象工廠模式不能用來創(chuàng)建具體對象,一般用它作為父類類創(chuàng)建一些子類。
// 抽象工廠方法 var VehicleFactory = function(subType, superType) { // 判斷抽象工廠中是否有該抽象類 if(typeof VehicleFactory[superType] === "function") { // 緩存類 function F() {}; // 繼承父類屬性和方法 F.prototype = new VehicleFactory[superType](); // 將子類構(gòu)造函數(shù)指向子類 subType.constructor = subType; // 子類原型繼承父類 subType.prototype = new F(); } else { // 不存在該抽象類拋出錯誤 throw new Error("未創(chuàng)建該抽象類"); } }; // 小汽車抽象類 VehicleFactory.Car = function() { this.type = "car"; }; VehicleFactory.Car.prototype = { getPrice: function() { return new Error("抽象方法不能調(diào)用") } }; // 公交車抽象類 VehicleFactory.Bus = function() { this.type = "bus"; }; VehicleFactory.Bus.prototype = { getPrice: function() { return new Error("抽象方法不能調(diào)用"); } };
抽象工廠實際上是一個子類繼承父類的方法,在該方法中需要通過傳遞子類以及繼承父類的名稱。
收獲與總結(jié)抽象工廠模式是設(shè)計模式中最抽象的一種,也是創(chuàng)建模式中唯一一種抽象化創(chuàng)建模式。該模式創(chuàng)建出的結(jié)果不是一個真實的對象實例,而是一個類簇,指定了類的結(jié)構(gòu)。
建造者模式建造者模式告訴我們分即是合。
定義將一個復(fù)雜對象的構(gòu)建層與其表示層相互分離,同樣的構(gòu)建過程可采用不同的表示。
應(yīng)用場景現(xiàn)在有一個發(fā)布簡歷的需求,就是幫別人在公司網(wǎng)站上發(fā)布簡歷,但是這些簡歷有一個需求,除了將興趣愛好以及一些特長發(fā)布在頁面里,其他信息如聯(lián)系方式等不要發(fā)布在網(wǎng)站上,而且每個人想找的工作是可以分類的。這樣一些需求我們需要創(chuàng)建的東西就多了,這時候前面的三種工廠模式都不適合了,這里就可以用建造者模式。
建造者模式和只關(guān)心創(chuàng)建結(jié)果的工廠模式不同,雖然其目的也是創(chuàng)建一個對象,但是更多關(guān)心的是創(chuàng)建這個對象的整個過程。在本例中,我們需要的不僅僅是應(yīng)聘者的實例還要在創(chuàng)建過程中注意這位應(yīng)聘者有哪些興趣愛好等。
// 創(chuàng)建一位人類 var Human = function(param) { // 技能 this.skill = param && param.skill || "保密"; // 興趣愛好 this.hobby = param && param.hobby || "保密"; }; // 類人原型方法 Human.prototype = { getSkill: function() { return this.skill; }, getHobby: function() { return this.hobby; } }; // 實例化姓名類 var Named = function(name) { var that = this; // 構(gòu)造器,解析姓名的姓與名 (function(name, that) { that.wholeName = name; if(name.indexOf(" ") > -1) { that.FirstName = name.slice(0, name.indexOf(" ")); that.FirstName = name.slice(name.indexOf(" ")); } })(name, that); }; // 實例化職位類 var Work = function(work) { var that = this; // 構(gòu)造器,通過傳入的職位特征來設(shè)置相應(yīng)職位及描述 (function(work, that) { switch(work) { case "code": that.work = "工程師"; break; case "UI": case "UE": that.work = "設(shè)計師"; break; case "teach": that.work = "教師"; break; default: that.work = work; } })(work, that); }; // 更換期望的職位 Work.prototype.changeWork = function(work) { this.work = work; };
下面來創(chuàng)建一位應(yīng)聘者
// 應(yīng)聘者創(chuàng)建類 var Person = function(name, work) { // 創(chuàng)建應(yīng)聘者緩存對象 var _person = new Human(); // 創(chuàng)建應(yīng)聘者姓名解析對象 _person.name = new Named(name); // 創(chuàng)建應(yīng)聘者期望職位 _person.work = new Work(work); // 返回創(chuàng)建的應(yīng)聘者對象 return _person; }收獲與總結(jié)
建造者模式和前面幾種創(chuàng)建型設(shè)計模式不同,它關(guān)心對象的整個創(chuàng)建過程,因此通常將創(chuàng)建對象的類模塊化,這樣使創(chuàng)建類的每一個模塊都可以得到靈活的運用與高質(zhì)量的復(fù)用。這種方式對于整個對象類的拆分無形中增加了結(jié)構(gòu)的復(fù)雜性,因此如果對象粒度很小,或者模塊間的復(fù)用率很低,不建議使用建造者模式。
原型模式原型模式是JavaScript語言之魂。
定義用原型實例指向創(chuàng)建對象的類,使用于創(chuàng)建新的對象的類共享原型對象的屬性以及方法。
使用場景還是關(guān)于子類繼承父類的問題,為了提高性能,對于每次創(chuàng)建的一些簡單的而又有差異化的屬性可以放在構(gòu)造函數(shù)中,將一些消耗資源比較大的方法放在基類的原型中,這樣就可以避免不必要的消耗,這就是原型模式的雛形。
原型模式更多的是用在對象的創(chuàng)建上,比如創(chuàng)建一個實例對象的構(gòu)造函數(shù)比較復(fù)雜或者耗時比較長,或者通過創(chuàng)建多個對象來實現(xiàn)。此時最好不要用new關(guān)鍵字去復(fù)制這些基類,可以通過對這些對象屬性或者方法進行復(fù)制來實現(xiàn)創(chuàng)建。首先要有一個原型對象的復(fù)制方法。
// 原型對象復(fù)制方法 function prototypeExtend() { var F = function() {}, args = arguments, i = 0, len = args.length; for (; i < len; i++) { // 遍歷每個模板對象中的屬性 for(var j in args[i]) { F.prototype[j] = args[i][j]; } } // 返回緩存類實例 return new F(); }
企鵝游戲中創(chuàng)建一個企鵝對象,如果沒有企鵝基類,只提供了一些動作模板對象,可以通過實現(xiàn)這些模板對象的繼承來創(chuàng)建一個企鵝實例對象。
var penguin = prototypeExtend({ speed: 20, swim: function() { console.log("游泳速度" + this.speed); }, run: function() { console.log("奔跑速度" + this.speed); } })
這樣通過prototypeExtend創(chuàng)建的就是一個對象,不用再用new去創(chuàng)建一個新的實例對象。
收獲與總結(jié)原型模式實際上也是一種繼承,可以讓多個對象分享同一個原型對象的屬性和方法,這種繼承的實現(xiàn)是不需要創(chuàng)建的,而是將原型對象分享給那些繼承的對象。原型對象更適合在創(chuàng)建復(fù)雜的對象時,對于那些需求一直在變化而導(dǎo)致對象結(jié)構(gòu)不停地改變時,將那些比較穩(wěn)定的屬性與方法共用而提取的繼承的實現(xiàn)。
單例模式哈哈,讓你感受下一個人的寂寞。
定義又被稱為單體模式,只允許實例化一次的對象類。有時也可以用一個對象來規(guī)劃一個命名空間,井井有條地管理對象上的屬性和方法。
使用場景單例模式應(yīng)該是JavaScript中最常見的一種設(shè)計模式了,經(jīng)常為我們提供一個命名空間,來防止不同的人命名變量的沖突。還可以用它來創(chuàng)建一個小型的代碼庫。
var A = { Util: { util_method1: function() {}, util_method2: function() {} }, Tool: { tool_method1: function() {}, tool_method2: function() {} }, Ajax: { ajax_method1: function() {}, ajax_method2: function() {} } ... }
如果想使用這個代碼庫,像下面這樣訪問即可:
A.Util.util_method1(); A.Tool.tool_method2();收獲與總結(jié)
單例模式有時也被稱為單體模式,它是只允許實例化一次的對象類,有時這么做也是為了節(jié)省系統(tǒng)資源。JavaScript中單例模式經(jīng)常作為命名空間對象來實現(xiàn),通過單例對象,我們可以將各個模塊的代碼井井有條地梳理在一起。
結(jié)構(gòu)型設(shè)計模式結(jié)構(gòu)型設(shè)計模式關(guān)注于如何將類或?qū)ο蠼M合成更大、更復(fù)雜的結(jié)構(gòu),以簡化設(shè)計。主要有外觀模式,適配器模式,代理模式,裝飾者模式,橋接模式,組合模式和享元模式。
外觀模式作者把這種模式比喻成一種套餐服務(wù)。
定義為一組復(fù)雜的子系統(tǒng)接口提供一個更高級的統(tǒng)一接口,通過這個接口使得對子系統(tǒng)接口的訪問更加容易。在JavaScript中有時也會用于對底層結(jié)構(gòu)兼容性做統(tǒng)一封裝來簡化用戶使用。
使用場景為頁面文檔document對象添加點擊事件時,如果直接用onclick來綁定事件,那么如果團隊中再有人要為document綁定click事件時,就會把之前綁定的那個時間覆蓋,因為這是DOM0級事件。我們應(yīng)該用DOM2級事件處理程序提供的addEventListener來實現(xiàn),然而老版本IE是不支持這個方法的,必須用attachEvent,這樣如果我們寫一個能兼容所有瀏覽器的方式操作起來就會更方便,這時候就可以用到外觀模式。為功能統(tǒng)一但方法不統(tǒng)一的接口提供一個統(tǒng)一的接口。
// 外觀模式實現(xiàn) function addEvent(dom, type, fn) { // 對于支持DOM2級事件處理程序的瀏覽器 if(dom.addEventListener) { dom.addEventListener(type, fn, false); // 對于不支持addEventListener但支持attachEvent的瀏覽器 } else if(dom.attachEvent) { dom.attachEvent("on" + type, fn); } else { dom["on" + type] = fn; } }
解決瀏覽器兼容問題只是外觀模式應(yīng)用的一部分,很多代碼庫中都是通過外觀模式來封裝多個功能,簡化底層造作方法的。
收獲與總結(jié)當(dāng)一個復(fù)雜的系統(tǒng)提供一系列復(fù)雜的接口方法時,為系統(tǒng)的管理方便會造成接口方法的使用及其復(fù)雜。通過外觀模式,對接口進行二次封裝可以隱藏其復(fù)雜性。
適配器模式聽到這個是的名字,有沒有想到水管彎彎的場景呢?
定義將一個類(對象)的接口(方法或者屬性)轉(zhuǎn)化成另外一個接口,以滿足用戶需求,使類(對象)之間接口的不兼容問題通過適配器得以解決。
使用場景公司有個活動頁面正在使用公司內(nèi)部開發(fā)的A框架,可是很多新來的同事使用A框架開發(fā)新的功能需求時總是感覺很吃力,而且能用的方法有限,為了讓新同事盡快融入項目的開發(fā),可以引入jQuery框架,由于A框架和jQuery框架很像,這樣就可以寫一個適配器而不需要將之前的代碼全用jQuery寫一遍。
適配器模式不僅在編程中很常見,在生活中這種模式也很常見,比如三角插頭充電器對于兩項插頭是不能用的,此時就需要一個三項轉(zhuǎn)兩項插頭電源適配器,這就是一種適配器模式,其實它就是為了兩個代碼庫所寫的代碼兼容運行而書寫的額外代碼。
JavaScript中適配器模式還能適配兩個代碼庫,適配參數(shù),適配數(shù)據(jù),適配服務(wù)端數(shù)據(jù)等。以參數(shù)適配為例。
function doSomeThing(name, title, age, color, size, prize){}
記住這些參數(shù)的順序是很困難的,所以我們經(jīng)常是以一個參數(shù)對象方式傳入的,如下所示:
/** * obj.name: name * obj.title: title * obj.age: age * obj.color: color * obj.size: size * obj.prize: prize ***/ function doSomeThing(obj){}
然而當(dāng)調(diào)用的時候也不能確定傳遞的參數(shù)是否完整,如有一些必須得參數(shù)沒有傳入,一些參數(shù)有默認值等,這個時候就可以用適配器來適配傳入的參數(shù)對象。
function doSomeThing(obj) { var _adapter = { name: "雨夜清荷", title: "設(shè)計模式", age: 24, color: "pink", size: 100, prize: 50 }; for(var i in _adapter) { _adapter[i] = obj[i] || _adapter[i]; } }收獲與總結(jié)
JavaScript中的適配器更多應(yīng)用在對象之間,為了使對象可用,通常會將對象拆分并重新包裝,這樣就要了解適配器對象的內(nèi)部結(jié)構(gòu),這也是與外觀模式的區(qū)別所在。
代理模式有沒有想到牛郎織女鵲橋相會的場景?
定義由于一個對象不能直接引用另一個對象,所以需要通過代理對象在這兩個對象之間起到中介作用。
使用場景跨域問題應(yīng)該是使用代理模式解決的一個最典型的問題。由于用戶模塊上傳的照片量越來越大,導(dǎo)致服務(wù)器需要將上傳模塊重新部署到另外一個域中,這就導(dǎo)致了跨域問題。我們可以將相冊頁面和上傳模塊所在的服務(wù)器抽象成兩個對象,想讓跨域兩端的對象之間實現(xiàn)通信,就需要找個代理對象來實現(xiàn)他們之間的通信。
代理對象有很多種,簡單一點的如img之類的標(biāo)簽通過src可以向其他域下的服務(wù)器發(fā)送請求。不過這類請求是get請求,是單向的,不會有響應(yīng)數(shù)據(jù)。另外一種代理對象的形式是通過script標(biāo)簽。而我們需要的代理對象,是對頁面與瀏覽器間通信的,JSONP就實現(xiàn)了一種代理模式。我們知道src屬性可以實現(xiàn)get請求,因此可以在src指向的url地址上添加一些字段信息,服務(wù)器獲取這些字段信息,相應(yīng)生成一分內(nèi)容。
// 前端瀏覽器頁面
// 另一個域下的服務(wù)器請求接口
這種方式可以想象成合理的一只小船,通過小船將你的請求發(fā)送給對岸,然后對岸的人們將數(shù)據(jù)放在小船里為你帶回來。
收獲與總結(jié)代理模式除了在跨域問題中有很多應(yīng)用外,有時對對象的實例化對資源的開銷很大,如頁面加載初期加載文件有很多,此時能夠延遲加載一些圖片對頁面首屏加載時間收益是很大的,通過代理可以先加載預(yù)覽圖片然后再加載開銷大的圖片。
由此可見,代理模式可以解決系統(tǒng)之間耦合度以及系統(tǒng)資源開銷大的問題,通過代理對象可以保護被代理對象,使被代理對象不受外界的影響。
顯然房子裝修就是一種典型的裝飾者模式。
定義在不改變原對象的基礎(chǔ)上,通過對其進行包裝擴展(添加屬性或者方法)使原有對象可以滿足用戶的更復(fù)雜需求。
使用場景靜止是相對的,運動是絕對的,所以沒有一成不變的需求。在實際項目開發(fā)中需求總在不斷變化,當(dāng)原有的功能已經(jīng)不能滿足用戶的需求時,我們要做的就是在這個基礎(chǔ)上添磚加瓦,設(shè)置新功能和屬性來滿足用戶提出的需求,這就是裝飾者模式要做的。
// 裝飾者 var decorator = function(input, fn) { // 獲取事件源 var input = document.getElementById(input); // 若事件源已經(jīng)綁定事件 if(typeof input.onclick === "function") { // 緩存事件源原有回調(diào)函數(shù) var oldClickFn = input.onclick; // 為事件源定義新的事件 input.onclick = function() { // 事件源原有回調(diào)函數(shù) oldClickFn(); // 執(zhí)行事件源新增回調(diào)函數(shù) fn(); } } else { input.onclick = fn; } }收獲與總結(jié)
除了裝飾者模式,適配器模式也可以對原有對象進行擴展,所不同的是適配器進行擴展很多時候是對對象內(nèi)部結(jié)構(gòu)的重組,因此了解其自身結(jié)構(gòu)是必須的。而裝飾者模式對對象的擴展是一種良性擴展,不用了解其具體實現(xiàn),只是在外部進行了一次封裝擴展。
橋接模式作者把這種模式比喻成城市間的公路。
定義在系統(tǒng)沿著多個維度變化的同時,又不增加其復(fù)雜度并已達到解耦。
使用場景有時候,頁面中一些小小細節(jié)的改變常常因邏輯相似而導(dǎo)致大片臃腫的代碼,讓頁面苦澀不堪。現(xiàn)在項目有一個需求,是要把頁面上部的用戶信息添加一些鼠標(biāo)劃過的特效,但是用戶信息由很多小組件組成,對于用戶名,鼠標(biāo)劃過直接改變背景色,但是像用戶等級、用戶消息這類部件只能改變里面的數(shù)字內(nèi)容,處理邏輯不太一樣。這樣就需要寫不少代碼,但是又會感覺很冗余。這時候,我們首先要提取共同點,對想的抽象邏輯做抽象提取處理。
對于用戶信息模塊的每一部分鼠標(biāo)滑過與鼠標(biāo)離開兩個事件的執(zhí)行函數(shù)有很大一部分是相似的,比如它們都處理每個部件中的某個元素,它們都是處理元素的字體顏色和背景顏色??梢詣?chuàng)建下面這樣一個函數(shù),解除this耦合。
function changeColor(dom, color, bg) { // 設(shè)置元素的字體顏色 dom.style.color = color; // 設(shè)置元素的背景顏色 dom.style.background = bg; }
接下來就是對具體元素綁定時間了,但是僅僅知道元素事件綁定與抽象提取的設(shè)置樣式方法changeColor是不夠的,需要用一個方法將他們鏈接起來,這個方法就是橋接方法,這種模式就是橋接模式。就像你開著車去沈陽,那么你就需要找到一條連接北京與沈陽的公路,才能順利往返兩地。
對于事件的橋接方法,可以用一個匿名函數(shù)來代替。
var spans = document.getElementsByTagName("span"); spans[0].onmouseover = function() { changeColor(this, "red", "#ffffd"); }收獲與總結(jié)
橋接模式最主要的特點是將實現(xiàn)層(如元素綁定事件)與抽象層(如修飾頁面UI邏輯)解耦分離,使兩部分可以獨立變化,橋接模式主要是對結(jié)構(gòu)之間的解耦。
組合模式作者把組合模式比喻成超值午餐,感覺很形象。
定義又稱部分-整體模式,將對象組合成樹形結(jié)構(gòu)以表示“部分整體”的層級結(jié)構(gòu)。組合模式使得用戶對單個對象和組合對象的使用具有一致性。
使用場景為強化首頁用戶體驗,項目經(jīng)理準(zhǔn)備在用戶首頁添加一個新聞模塊,當(dāng)然新聞的內(nèi)容是根據(jù)用戶平時關(guān)注的內(nèi)容挖掘的,因此有的人可能會顯示文字新聞,有的人可能會是圖片新聞等等。
我們先來仔細分析下這個需求,需求中的這些新聞大致可以分為相互獨立的幾種類型,對某類新聞做修改時不會影響到其他類的新聞,這樣可以將每一類新聞抽象成面向?qū)ο缶幊讨械囊粋€類,然后在這些新聞類中挑選一些組合成需要的模塊,這時候就可以用組合模式了。
在頁面中,組合模式更常用在創(chuàng)建表單上,比如注冊頁面可能有不同的表單提交模塊。對于這些需求,我們只需要有一個基本的個體,然后通過一定的組合即可實現(xiàn)。
組合模式能夠給我們提供一個清晰的組成結(jié)構(gòu),組合對象類通過繼承同一個父類使其具有統(tǒng)一的方法,這樣也方便了統(tǒng)一管理與使用。
享元模式作者把享元模式比喻成城市公交車,可以仔細思考一番。
定義運用共享技術(shù)有效地支持大量的細粒度的對象,避免對象間擁有相同內(nèi)容造成多余的開銷。
使用場景現(xiàn)在有新聞的內(nèi)容太多,我們有了一個分頁顯示所有新聞的需求。一個簡單直觀的做法就是頁面加載后異步請求新聞數(shù)據(jù),然后創(chuàng)建所有條新聞插入頁面中,需要顯示哪一頁就顯示哪一頁。但是這樣做有一個很大的問題,這樣一下子創(chuàng)建幾百條新聞同時插入頁面會造成多頁的開銷嚴重影響網(wǎng)頁的性能。這里的所有新聞都有相似的結(jié)構(gòu),只是內(nèi)容不同罷了,對于這種相同結(jié)構(gòu)造成多余開銷的問題,可以用享元模式來解決。
享元模式 主要是對其數(shù)據(jù)、方法共享分離,將數(shù)據(jù)和方法分成內(nèi)部數(shù)據(jù)、內(nèi)部方法和外部數(shù)據(jù)、外部方法。內(nèi)部方法與內(nèi)部數(shù)據(jù)指的是相似或共有的數(shù)據(jù)和方法,所以將其提取出來減少開銷。上面例子中,所有新聞個體都有共同的結(jié)構(gòu),應(yīng)該作為內(nèi)部數(shù)據(jù),而下一頁按鈕綁定的事件則是外部方法。同時為了使用內(nèi)部數(shù)據(jù)還需要提供一個操作方法。
var Flyweight = function() { // 已創(chuàng)建的元素 var created = []; // 創(chuàng)建一個新聞包裝容器 function create() { var dom = document.createElement("div"); // 將容器插入新聞列表容器中 document.getElementById("container").appendChild(dom); // 緩存新創(chuàng)建的元素 created.push(dom); // 返回創(chuàng)建的新元素 return dom; } return { // 獲取創(chuàng)建新聞元素方法 getDiv: function() { // 如果已創(chuàng)建的元素小于當(dāng)前頁元素總個數(shù)(5個),則創(chuàng)建 if(created.length < 5) { return created(); } else { // 獲取第一個元素,并插入去后面 var div = created.shift(); created.push(div); return div; } } } }
上面創(chuàng)建一個享元類,由于每頁只能顯示5條新聞,所以創(chuàng)建5個元素,保存在享元類內(nèi)部,可以通過getDiv方法來獲取創(chuàng)建的元素。下面就要實現(xiàn)外部數(shù)據(jù)和外部方法,外部數(shù)據(jù)就是我們要顯示的所有新聞內(nèi)容,由于每個內(nèi)容都不一樣肯定不能共享。首先,我們要根據(jù)新聞內(nèi)容實例化頁面,然后,對下一頁綁定一個點擊事件,顯示下一頁。
var paper = 0, num = 5, len = article.length; // 添加五條新聞 for(var i = 0; i < 5; i++) { if(article[i]) // 通過享元類獲取創(chuàng)建的元素并寫入新聞內(nèi)容 Flyweight.getDiv().innerHTML = article[i]; }
// 下一頁按鈕綁定事件 document.getElementById("next_page").onclick = function() { // 如果新聞內(nèi)容不足5條則返回 if(article.length < 5) { return; } var n = ++paper * num % len, // 獲取當(dāng)前頁的第一條新聞索引 j = 0; // 插入5條新聞 for(; j < 5; j++) { // 如果存在n+j條則插入 if(article[n + j]) { Flyweight.getDiv().innerHTML = article[n + j]; // 否則插入起始位置第n+j-len條 } else if(article[n + j - len]) { Flyweight.getDiv().innerHTML = article[n + j - len]; } else { Flyweight.getDiv().innerHTML = ""; } } }
這樣用享元模式對頁面重構(gòu)之后每次操作只需要操作5個元素,這樣性能可以提高很多。
收獲與總結(jié)享元模式的應(yīng)用是為了提高程序的執(zhí)行效率與系統(tǒng)性能,因此在大型系統(tǒng)開發(fā)中應(yīng)用比較廣泛,可以避免程序中的數(shù)據(jù)重復(fù)。應(yīng)用時一定要找準(zhǔn)內(nèi)部狀態(tài)與外部狀態(tài),這樣才能更合理地提取分離。
行為型設(shè)計模式行為型設(shè)計模式用于不同對象之間職責(zé)劃分或算法抽象,行為型設(shè)計模式不僅僅涉及類和對象,還涉及類或?qū)ο笾g的交流模式并加以實現(xiàn)。行為型設(shè)計模式主要有模板方法模式,觀察者模式,狀態(tài)模式,策略模式,職責(zé)鏈模式,命令模式,訪問者模式,中介者模式,備忘錄模式,迭代器模式和解釋器模式,這么多的模式真得好好消化一陣子了。
模板方法模式作者把這種模式比喻成照貓畫虎。
定義父類中定義一組操作算法骨架,而將一些實現(xiàn)步驟延遲到子類,使得子類可以不改變父類算法結(jié)構(gòu)的同時可重新定義算法中某些實現(xiàn)步驟。
使用場景提示框歸一化,一個網(wǎng)站有很多頁面,如果每個頁面的彈出框樣式不太一致就會顯得不是很和諧,需要將他們的樣式統(tǒng)一。新手最直觀的想法就是去每個頁面一個個修改,當(dāng)然這樣的代價是很大的,我們需要寫一個彈出框插件,將這些彈出框封裝好,然后再各個頁面調(diào)用即可。這是在這個插件中就可以使用模板方法模式了,不需要重復(fù)寫多個樣式。
模板方法模式就是將多個模型抽象畫歸一,從中抽象出一個最基本的模板,這個模板可以作為實體也可以作為抽象對象,其他模塊只需要繼承這個模板方法,也可以擴展某些方法。
打個比方,我們生活中用蛋糕做模具做蛋糕,做出的蛋糕是外形相同的,因為他們都用同一個模具。然而商店里面賣的蛋糕是各式各樣的,這都是對蛋糕的二次加工。我們的需求中基本提示框就是我們抽象出來的模具,其他提示框比這個提示框要多一些功能,我們只需要對他們做一些二次加工就能滿足需求了。
模板方法不僅在歸一化組件時使用,有時候創(chuàng)建頁面時也是很常用的,比如創(chuàng)建三類導(dǎo)航,第一類是基礎(chǔ)的,第二類是多了消息提醒功能的,第三類多了后面顯示網(wǎng)址功能。這也可以用模板方法實現(xiàn),此時抽象出來的基類是最簡單的基礎(chǔ)導(dǎo)航類。
// 格式化字符串方法 function formateString(str, data) { return str.replace(/{#(w+)#}/g, function(match, key) { return typeof data[key] === undefined ? "": data[key] }); } // 基礎(chǔ)導(dǎo)航 var Nav = function(data) { // 基礎(chǔ)導(dǎo)航樣式模板 this.item = "{#name#}"; // 創(chuàng)建字符串 this.html = ""; // 格式化數(shù)據(jù) for(var i = 0, len = data.length; i < len; i++) { this.html += formateString(this.item, data[i]); } // 返回字符串?dāng)?shù)據(jù) return this.html; }
對于消息提醒導(dǎo)航類,只需額外添加消息提醒組件模板,并與消息提醒組件模板對傳入的網(wǎng)址數(shù)據(jù)進行裝飾,得到所需的字符串,在調(diào)用從基類繼承的方法處理這些字符串即可。
var NumNav = function(data) { // 消息提醒信息組件模板 var tpl = "{#num#}"; // 裝飾數(shù)據(jù) for(var i = data.length - 1; i >= 0; i--) { data[i].name += data[i].name + formateString(tpl, data[i]); } // 繼承基礎(chǔ)導(dǎo)航類 return Nav.call(this, data); }收獲與總結(jié)
模板方法的核心在于對方法的重用,將核心方法封裝在基類中,讓子類繼承基類的方法,實現(xiàn)基類方法的共享,達到方法共用。子類繼承的方法是可擴展的,這就需要對基類繼承的方法進行重寫。
觀察者模式作者把這種模式比喻成通信衛(wèi)星。
定義又被稱作發(fā)布-訂閱模式或消息機制,定義了一種依賴關(guān)系,解決了主體對象與觀察者之間功能的耦合。
使用場景在團隊開發(fā)中,經(jīng)常是一個人負責(zé)一個模塊,那么每人負責(zé)的模塊之間要如何進行溝通呢?比如你實現(xiàn)一些需求需要添加一些代碼,但是這個需求需要其他模塊配合,但是每個模塊都是不同人寫的,你不想因為新添加的代碼影響到他人實現(xiàn)的功能,這個時候就需要用到觀察者模式了。
觀察者模式就是為了解決主體對象與觀察者之間的耦合。打個比方,目前每個國家都在研發(fā)并發(fā)射衛(wèi)星,發(fā)射這些衛(wèi)星是為了監(jiān)控一些信息,那么它就可以被看做一個觀察者或者說是一個消息系統(tǒng),如果讓這顆衛(wèi)星為飛機導(dǎo)航,那么這架飛機就是一個被觀察者或者說是一個主體對象。那么如果地面上的中轉(zhuǎn)站或者其他飛機需要知道這架飛機的信息,于是每當(dāng)飛機到達一個地方時就會向衛(wèi)星發(fā)出位子信息,然后衛(wèi)星又將信息廣播到已經(jīng)訂閱這架飛機的中轉(zhuǎn)站,這樣就可以避免一些飛機事故發(fā)生。
這時候,觀察者至少需要有兩個方法,一個是接收某架飛機發(fā)來的消息,一個是向訂閱的中轉(zhuǎn)站發(fā)送響應(yīng)消息。但是,并不是每個中轉(zhuǎn)站都要時刻監(jiān)控飛機狀態(tài)的,所以還需要一個取消注冊的方法。當(dāng)然這些消息還需要保存,就需要一個保存消息的容器。這時候觀察者雛形就出來了,他有一個消息容器和三個方法,訂閱消息方法,取消訂閱消息方法,發(fā)送訂閱消息方法。
var Observer = (function() { // 防止消息隊列暴露而被篡改,故將消息容器作為靜態(tài)私有變量保存 var __messages = {}; return { // 注冊信息接口 regist: function() {}, // 發(fā)布信息接口 fire: function() {}, // 移除信息接口 remove: function() {} } })();
下面就是可以自己具體實現(xiàn)這些接口了。
收獲與總結(jié)觀察者模式最主要是解決類或?qū)ο笾g的耦合,解耦兩個互相依賴的對象,使其依賴于觀察者的消息機制。這樣對于任何一個訂閱者來說,其他訂閱者對象的改變不會影響到自身,其自身既可以是消息的發(fā)出者也可以是消息的執(zhí)行者,這都依賴于調(diào)用觀察者對象中的三種方法(訂閱,注銷,發(fā)布消息)中的哪一種。
狀態(tài)模式作者把這種模式比喻成超級瑪麗。
定義當(dāng)一個對象內(nèi)部狀態(tài)發(fā)生改變時,會導(dǎo)致其行為的改變,這看起來像是改變了對像。
使用場景平時寫代碼的時候經(jīng)常會遇到要寫很多條件判斷語句的情況,那么怎么減少代碼中的條件判斷語句呢?對于這類分支條件內(nèi)部獨立結(jié)果的管理,可以使用狀態(tài)模式,每一種條件作為對象的一種狀態(tài),面對不同的判斷結(jié)果,其實就是選擇對象內(nèi)的一種狀態(tài)。
將不同的判斷結(jié)果封裝在狀態(tài)對象內(nèi),然后該狀態(tài)對象返回一個可被調(diào)用的接口方法,用于調(diào)用狀態(tài)對象內(nèi)部的某種方法。
// 投票結(jié)果狀態(tài)對象 var ResultState = function() { // 判斷結(jié)果保存在內(nèi)部狀態(tài)中 var States = { // 每種狀態(tài)作為一種獨立方法保存 state0: function() { console.log("這是第一種情況"): }, state1: function() { console.log("這是第二種情況"): }, state2: function() { console.log("這是第三種情況"): }, state3: function() { console.log("這是第四種情況"): } } // 獲取某種狀態(tài)并執(zhí)行對應(yīng)方法 function show(result) { States["state" + result] && States["state" + result](); } return { // 返回調(diào)用狀態(tài)方法接口 show: show } }();
想調(diào)用第三種結(jié)果就可以如下調(diào)用
ResultState.show(3);
對于狀態(tài)模式,主要目的就是將條件判斷的不同結(jié)果轉(zhuǎn)化為狀態(tài)對象的內(nèi)部狀態(tài),這個內(nèi)部狀態(tài)一般作為狀態(tài)對象的私有變量,然后提供一個能夠調(diào)用狀態(tài)對象內(nèi)部狀態(tài)的接口方法對象即可。
收獲與總結(jié)狀態(tài)模式既是解決程序中臃腫的分支判斷語句問題,將每一個分支轉(zhuǎn)化為一種狀態(tài)獨立出來,方便每種狀態(tài)的管理又不至于每次只需時遍歷所有分支。
策略模式作者把這種模式比喻成活諸葛。
定義將定義的一組算法封裝起來,使其相互之間可以替換。封裝的算法具有一定獨立性,不會隨客戶端變化而變化。
使用場景年底的時候,公司商品展銷頁都要開展大促銷活動。在圣誕節(jié),一部分商品5折出售,一部分商品8折出售,一部分商品9折出售,到元旦搞個幸運反饋活動,普通用戶滿100返30,高級VIP用戶滿100返50。這個時候上面的狀態(tài)模式就不適用了,因為每一天每一個商品只有一種促銷情況,這個時候可以用策略模式。
結(jié)構(gòu)上看,它與狀態(tài)模式很像,也是在內(nèi)部封裝一個對象,然后通過返回的接口對象實現(xiàn)實現(xiàn)對內(nèi)部對象的調(diào)用,不同點是,策略模式不需要管理狀態(tài)、狀態(tài)間沒有依賴關(guān)系、策略之劍可以相互替換、在策略對象內(nèi)部保存的是相互獨立的一些算法??纯床呗詫ο蟮膶崿F(xiàn):
// 價格策略對象 var PriceStrategy = function() { // 內(nèi)部算法對象 var strategy = { // 100返30 return30: function(price) {}, // 100返50 return50: function(price) {}, // 9折 percent90: function(price) {}, // 8折 percent80: function(price) {}, // 5折 percent50: function(price) {}, } // 策略算法調(diào)用接口 return function(algorithm, price) { return strategy[algorithm] && strategy[algorithm](price); } }();收獲與總結(jié)
策略模式主要特色是創(chuàng)建一系列策略算法,每組算法處理業(yè)務(wù)都是相同的,只是處理的過程或者處理的結(jié)果不一樣,所以它們是可以相互替換的,這樣就解決了算法與使用者之間的耦合。
職責(zé)鏈模式作者把這種模式比喻成一個有序車站。
定義解決請求的發(fā)送者與請求的接受者之間的耦合,通過職責(zé)鏈上的多個對象對分解請求流程,實現(xiàn)請求在多個對象之間的傳遞,知道最后一個對象完成請求的處理。
使用場景項目經(jīng)理準(zhǔn)備改善頁面中的輸入驗證與提示交互體驗。如用戶在輸入框輸入信息后,在輸入框的下面提示出一些備選項,當(dāng)用戶輸入完成后,則要對用戶輸入信息進行驗證等,頁面中很多模塊需要用戶提交信息,為增強用戶體驗,這些輸入框大部分需要具備以上兩種功能。現(xiàn)在需要完成這個需求,但是以后可能要對原有表單交互體驗做一些修改,也就是這是一個半成品需求。這種情況下,我們需要將需求里面需要做的每一件事情獨立出來,這樣完整的需求就變成一個個相互獨立的模塊需求,這樣就不會因為以后需求的改變而影響我們項目的進展,這樣還有利于以后的單元測試。這其實就是一種職責(zé)鏈模式。
對于上面的需求,對輸入框綁定事件是第一部分,第二部分是創(chuàng)建xhr進行異步數(shù)據(jù)獲取,第三部分就是適配響應(yīng)數(shù)據(jù),將接收到的數(shù)據(jù)格式化成可處理的形式,最后一部分是向組件創(chuàng)建器傳入相應(yīng)數(shù)據(jù)生成組件。
職責(zé)鏈模式定義了請求的傳遞方向,通過多個對象對請求的傳遞,實現(xiàn)一個復(fù)雜的邏輯操作。因此職責(zé)鏈模式將負責(zé)的需求顆?;鹨粚崿F(xiàn)每個最小分內(nèi)的需求,并將請求順序地傳遞。對于職責(zé)鏈上的每一個對象來說,它可能是請求的發(fā)起者也可能是請求的接收者,通過這種方式不僅僅簡化原對象的復(fù)雜度,而且解決原請求的發(fā)起者與原請求的接收者之間的耦合。
命令模式 定義將請求與實現(xiàn)解耦并封裝成獨立對象,從而使不同的請求對客戶端的實現(xiàn)參數(shù)化。
使用場景現(xiàn)在的需求是要做一個活動頁面,平鋪式的結(jié)構(gòu),不過頁面的每個模塊都有些相似的地方,比如每個預(yù)覽產(chǎn)品圖片區(qū)域,都有一行標(biāo)題,然后標(biāo)題下面是產(chǎn)品圖片,只是圖片的數(shù)量與排列不同。我們需要一種自由創(chuàng)建視圖模塊的方法,有時候創(chuàng)建多張圖片有時候只創(chuàng)建一張圖片,這時候可以試試命令模式。
命令模式是將創(chuàng)建模塊的邏輯封裝在一個對象里,這個對象提供一個參數(shù)化的請求接口,通過調(diào)用這個接口并傳遞一些參數(shù)實現(xiàn)調(diào)用命令對象內(nèi)部中的一些方法。請求部分很簡單,只需要按照給定參數(shù)格式書寫指令即可,所以實現(xiàn)部分的封裝才是重點,因為它要為請求部分提供所需方法。
那么哪些對象需要被命令化呢?既然需要動態(tài)展示不同模塊,所以創(chuàng)建元素這一需求就是變化的,因此創(chuàng)建元素方法、展示方法應(yīng)該被命令化。
// 模塊實現(xiàn)模塊 var viewCommand = (function() { var tpl = { // 展示圖片結(jié)構(gòu)模塊 product: [ "",.....,"" ].join(""), // 展示標(biāo)題結(jié)構(gòu)模塊 title: [ "",.....,"" ].join(""), }, // 格式化字符串緩存字符串 html = ""; // 格式化字符串 function formateString(str, obj) {} // 方法集合 var Action = { // 創(chuàng)建方法 create: function(data, view) { // 解析數(shù)據(jù) if(data.length) { // 遍歷 for(var i = 0, len = data.length; i < len; i++) { html += formateString(tpl[view], data[i]); } } else { html += formateString(tpl[view], data); } }, // 展示方法 display: function(container, data, vuew) { // 如果傳入數(shù)據(jù) if(data) { // 根據(jù)給的數(shù)據(jù)創(chuàng)建視圖 this.create(data, view); } // 展示模塊 document.getElementById(container).innerHTML = html; // 展示后清空緩存字符串 html = ""; } } // 命令接口 return function excute(msg) { // 解析命令,如果msg.param不是數(shù)組則將其轉(zhuǎn)化為數(shù)組 msg.param = Object.prototype.toString.call(msg.param) === "[object Array]" ? msg.param : [msg.param]; // Action內(nèi)部調(diào)用的方法引用this,此處保證作用域this執(zhí)行傳入Action Action[msg.command].apply(Action, msg.param) } })();
下面就可以測試這個命令對象了:
var productData = [ { src: "command/02.jpg", text: "綻放的桃花" }, { src: "command/03.jpg", text: "陽光下的溫馨" } ], // 模塊標(biāo)題數(shù)據(jù) titleData = { title: "夏日里的一片溫馨", tips: "暖暖的溫情帶給人們家的感覺" } // 調(diào)用命令對象 viewCommand({ command: "display", param: ["title", titleData, "title"] }); viewCommand({ command: "create", param: ["product", productData, "product"] });
有了命令模式,想創(chuàng)建任何頁面視圖都是一件很簡單的事情。
收獲與總結(jié)命令模式是將執(zhí)行的命令封裝,解決命令發(fā)起者與命令執(zhí)行者之間的耦合,每一條命令實質(zhì)上是一個操作。命令的是使用者不必了解命令執(zhí)行者的命令接口是如何實現(xiàn)的,只需要知道如何調(diào)用。
訪問者模式作者把這種模式比喻成駐華大使。
定義針對于對象結(jié)構(gòu)中的元素,定義在不改變對象的前提下訪問結(jié)構(gòu)中元素的新方法。
使用場景用DOM2級事件為頁面中元素綁定事件時,為css設(shè)置一些樣式如下:
var bindEvent = function(dom, type, fn) { if(dom.addEventListener) { dom.addEventListener(type, fn, false); } else if(dom.attachEvent) { dom.attachEvent("on" + type, fn); } else { dom["on" + type] = fn; } } var demo = document.getElementById("demo"); bindEvent(demo, "click", function() { this.style.background = "red"; });
這個在IE瀏覽器中會出問題,因為IE的attachEvent事件中this指向的竟然是window而不是這個元素,所以如果想獲取事件對象必須用window.e來獲取。這個問題可以借用訪問者模式來解決。
訪問者模式的思想是我們在不改變操作對象的同時,為它添加新的操作方法,來實現(xiàn)對操作對象的訪問。下面看看IE的實現(xiàn)方式:
function bindIEEvent(dom, type, fn, data) { var data = data || {}; dom.attachEvent("on" + type, function(e){ fn.call(dom, e, data); }); };
上面實現(xiàn)方法的核心就是調(diào)用call方法,call方法的作用就是更改函數(shù)執(zhí)行時的作用域,這正是訪問者模式的精髓。
收獲與總結(jié)訪問者模式解決數(shù)據(jù)與數(shù)據(jù)操作方法之間的耦合,將數(shù)據(jù)的操作方法獨立于數(shù)據(jù),使其可以自由化演變。訪問者更適合那些數(shù)據(jù)穩(wěn)定但是數(shù)據(jù)的操作方法易變的環(huán)境下。
中介者模式作者把這種模式比喻成媒婆,好吧,我笑了這里。
定義通過中介者對象封裝一系列對象之間的交互,是對象之間不再相互引用,降低他們之間的耦合。有時中介者對象也可以改變對象之間的交互。
使用場景項目經(jīng)理準(zhǔn)備在用戶首頁上的導(dǎo)航模塊添加一個設(shè)置層,讓用戶可以通過設(shè)置層來設(shè)置導(dǎo)航展開樣式。但是頁面中好多模塊都有導(dǎo)航,這要改起來工作量也很大,上面講的觀察者模式雖然能解決模塊之間的耦合,但是這里我們并沒有需要向設(shè)置層發(fā)送請求的需求,設(shè)置層只是單向控制導(dǎo)航模塊內(nèi)導(dǎo)航的樣式。這樣的單向通信就可以使用中介者模式。
觀察者模式和中介者模式都是通過消息收發(fā)機制實現(xiàn),不過在觀察者模式中,一個對象既可以是消息的發(fā)送者也可以是消息的接收者,而中介者模式中消息的發(fā)送方只有一個就是中介者對象,而且中介者對象不能訂閱消息,只有那些活躍對象(訂閱者)才能訂閱中介者消息。
如果用中介者模式來解決上面的問題,那么中介者對象就是設(shè)置層模塊對象,它負責(zé)向各個導(dǎo)航模塊對象發(fā)送用戶設(shè)置消息,而各個導(dǎo)航模塊則應(yīng)該作為消息的訂閱者存在,實現(xiàn)如下:
// 中介者對象 var Mediator = function() { // 消息對象 var _msg = {}; return { // 訂閱消息方法,type:消息名稱 action:消息回調(diào)函數(shù) register: function(type, action) { // 如果消息存在 if(_msg[type]) // 存入回調(diào)函數(shù) _msg[type].push(action); else { // 不存在則建立消息容器 _msg[type] = []; _msg[type].push(action); } }, // 發(fā)布消息方法 send: function(type) { // 如果該消息已經(jīng)被訂閱 if(_msg[type]) { // 遍歷已存儲的消息回調(diào)函數(shù) for(var i = 0, len = _msg[type].length; i < len; i++) { // 執(zhí)行回調(diào)函數(shù) _msg[type][i] && _msg[type][i](); } } } } }();
這樣就創(chuàng)建了一個中介者對象,下面就可以利用這個中介者對象完成我們的需求了。
收獲與總結(jié)同觀察者模式一樣,中介者模式的主要業(yè)務(wù)也是通過模塊間或者對象間的復(fù)雜通信,來解決模塊間或?qū)ο箝g的耦合。在中介者模式中,訂閱者是單向的,只能是訂閱者而不能是發(fā)布者。而消息統(tǒng)一由中介者對象發(fā)布。
備忘錄模式 定義在不破壞對象的封裝性的前提下,在對象之外捕獲并保存該對象內(nèi)部狀態(tài)以便日后對象使用或者對象恢復(fù)到以前的某個狀態(tài)。
使用場景在前面提到的新聞頁面中,有上一頁和下一頁的按鈕,頁面的內(nèi)容是用異步請求獲取的。如果點擊下一頁按鈕接著再點擊上一頁那么之前那一頁又要進行一次異步請求,這是多余的操作。因為第一次已經(jīng)獲取了數(shù)據(jù),不需要再發(fā)送多余的請求。這個時候可以用備忘錄模式來緩存請求過的數(shù)據(jù)。也就是說每次發(fā)生請求的時候?qū)Ξ?dāng)前狀態(tài)做一次記錄,將請求到的數(shù)據(jù)以及對應(yīng)得頁碼緩存下來,如果之后返回到之前瀏覽過的頁面,直接在緩存中查詢即可,不用發(fā)生異步請求。先創(chuàng)建一個新聞緩存器:
// Page備忘錄類 var Page = function() { // 信息緩存對象 var cache = {}; return function(page, fn) { // 判斷該頁數(shù)據(jù)是否在緩存中 if(cache[page]) { // 顯示該頁內(nèi)容 showPage(page, cache[page]); // 執(zhí)行成功回調(diào)函數(shù) fn && fn(); } else { // 否則異步請求 $.post("./data/getNewsData.php", { page: page }, function(res) { // 成功返回 if(res.errNo == 0) { showPage(page, res.data); cache[page] = res.data; fn && fn(); } else { // 處理異常 } }) } } }
上面代碼可以看出Page緩存器內(nèi)部緩存了每次請求回來的新聞數(shù)據(jù),這樣以后如果用戶想回看某頁新聞數(shù)據(jù)就不需要發(fā)送不必要的請求了。
收獲與總結(jié)備忘錄模式最主要的任務(wù)是對現(xiàn)有的數(shù)據(jù)或狀態(tài)進行緩存,為將類某個時刻使用或恢復(fù)做準(zhǔn)備。但是當(dāng)數(shù)據(jù)量過大時,會嚴重占用系統(tǒng)提供的資源,此時對緩存器的優(yōu)化是很有必要的,復(fù)用率低的數(shù)據(jù)緩存下來是不值得的。
迭代器模式作者把這種模式比喻成一個點鈔機。
定義在不暴露對象內(nèi)部結(jié)構(gòu)的同時,可以順序地訪問聚合對象內(nèi)部的元素。
使用場景迭代器模式主要是解決重復(fù)循環(huán)迭代的問題,之前接觸過面向?qū)ο笳Z言的應(yīng)該都對迭代器有所了解。迭代器就是用來順序地訪問一個聚合對象內(nèi)部元素的,它可以簡化我們遍歷操作,就行銀行里的點鈔機,有了它可以大幅度降低我們的點鈔成本。下面創(chuàng)建一個常用的迭代器對象:
var Iterator = function(items, container) { // 獲取父元素 var container = container && document.getElementById(container) || document, // 獲取元素 items = container.getElementsByTagName(items), // 獲取元素長度 length = items.length, // 當(dāng)前索引值 index = 0; // 緩存原生數(shù)組splice方法 var splice = [].splice; return { // 獲取第一個元素 first: function() {}, // 獲取最后一個元素 second: function() {}, // 獲取前一個元素 pre: function() {}, // 獲取后一個元素 next: function() {}, // 獲取某一個元素 get: function(num) {}, // 對每一個元素執(zhí)行某一個方法 dealEach: function(fn) {}, // 對某一個元素執(zhí)行某一個方法 dealItem: function(num, fn) {}, // 排他方式處理某一個元素 exclusive: function() {} } }
下面具體實現(xiàn)迭代器里面的這些方法,然后就可以用這個迭代器對象啦。
收獲與總結(jié)通過迭代器我們可以順序地訪問一個聚合對象中的每一個元素。在開發(fā)中,迭代器極大簡化了代碼中的循環(huán)語句,使代碼結(jié)構(gòu)清晰緊湊。用迭代器去處理一個對象時,只需要提供處理的方法,而不必去關(guān)心對象的內(nèi)部結(jié)構(gòu),這也解決了對象的使用者與對象內(nèi)部結(jié)構(gòu)之間的耦合。
解釋器模式 定義對于一種語言,給出其文法表示,并定義一種解釋器,通過使用這種解釋器來解釋語言中定義的句子。
使用場景一個頁面中的某些功能好壞有時是靠一定的數(shù)據(jù)依據(jù)支撐的。項目經(jīng)理想看看用戶對最近新增的功能使用情況,前后端要給出統(tǒng)計數(shù)據(jù),然而前端交互統(tǒng)計項中要給出交互元素路徑。這件事情與冒泡事件類似,只不過在這個路徑中還要關(guān)心同一層級中當(dāng)前元素的兄弟元素。比如下面的結(jié)構(gòu):
要獲取button相對于class為wrap的div元素的Xpath路徑,那么可以表示為DIV>DIV2>SPAN。
上面對需求的描述是一種文法,描述的是一組規(guī)則,現(xiàn)在要做的事實現(xiàn)一個規(guī)則解釋器來解釋上面的規(guī)則。首先要分析給出的文法,查找他們的相似點,然后該清楚我們要先實現(xiàn)什么再實現(xiàn)什么,基本上問題就能解決了。
一些描述性語句,幾次功能的提取抽象,形成了一套語法法則,這就是解釋器模式要處理的事情。是否能應(yīng)用解釋器模式的一條重要準(zhǔn)則是能否根據(jù)需求解析出一套完整的語法規(guī)則,不論該語法規(guī)則簡單或是復(fù)雜都是必須的。
技巧型設(shè)計模式技巧型設(shè)計模式是通過一些特定技巧來解決組件的某些方面的問題,這類技巧一般通過實踐經(jīng)驗總結(jié)得到。這本書中總結(jié)了8種技巧型設(shè)計模式,分別是鏈模式,委托模式,數(shù)據(jù)訪問對象模式,節(jié)流模式,簡單模板模式,惰性模式,參與者模式和等待者模式。有興趣的同學(xué)可以去買書來看哦,這里就不一一解釋了。
架構(gòu)型設(shè)計模式架構(gòu)型設(shè)計模式是一類框架結(jié)構(gòu),通過提供一些子系統(tǒng),指定它們的職責(zé),并將它們條理清晰地組織在一起。現(xiàn)在流行的前端框架都用了這種類型的設(shè)計模式。本書總結(jié)了6種架構(gòu)型設(shè)計模式,分別是同步模塊模式,異步模塊模式,Widget模式,MVC模式,MVP模式和MVVM模式。
學(xué)習(xí)設(shè)計模式的學(xué)習(xí)對于我們來說任重而道遠,我們需要在實踐中不斷思考不斷總結(jié)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/86616.html
摘要:詞法熟悉語法的開發(fā)者,箭頭函數(shù)在涉及綁定時的行為和普通函數(shù)的行為完全不一致。被忽略的作為的綁定對象傳入,使用的是默認綁定規(guī)則。使用內(nèi)置遍歷數(shù)組返回迭代器函數(shù)普通對象不含有,無法使用,可以進行改造,個人博客地址 this詞法 熟悉ES6語法的開發(fā)者,箭頭函數(shù)在涉及this綁定時的行為和普通函數(shù)的行為完全不一致。跟普通this綁定規(guī)則不一樣,它使用了當(dāng)前的詞法作用域覆蓋了this本來的值。...
摘要:代碼之髓讀后感如何高效的學(xué)習(xí)語言技術(shù)讀后感王垠如何掌握程序語言代碼之髓這本書里提出了三種學(xué)習(xí)語言的方法如何高效的學(xué)習(xí)語言在比較中學(xué)習(xí)在歷史中學(xué)習(xí)在實踐中學(xué)習(xí)在比較中學(xué)習(xí)通過比較多種語言,總結(jié)出某種語言的獨有特點,以及多種語言的共有特點。 title: 代碼之髓讀后感——如何高效的學(xué)習(xí)語言date: 2017-07-08 17:17:00categories: 技術(shù)tags: 讀后感 ...
摘要:遮蔽效應(yīng)作用域查找會在找到第一個匹配的標(biāo)識符時停止,不會繼續(xù)往上層作用域查找,這就會產(chǎn)生遮蔽效應(yīng)。會發(fā)現(xiàn)每一次輸出的都是為啥勒所有的回調(diào)函數(shù)回在循環(huán)結(jié)束后才會執(zhí)行事件循環(huán)。 三劍客 編譯,顧名思義,就是源代碼執(zhí)行前會經(jīng)歷的過程,分三個步驟, 分詞/詞法分析,將我們寫的代碼字符串分解成多個詞法單元 解析/語法分析,將詞法單元集合生成抽象語法樹(AST) 代碼生成,抽象語法樹(AST)轉(zhuǎn)...
摘要:今天,我無意中看到這樣一個東西,它叫做,這是一個開源免費的適用于各種移動端的觸摸滑動插件。同時導(dǎo)航欄也是可以手動滑動的,當(dāng)用戶手動滑動導(dǎo)航欄,點擊某一個板塊時,下面的內(nèi)容部分會隨即滑到相應(yīng)的內(nèi)容塊。 今天,我無意中看到這樣一個東西,它叫做Swiper,這是一個開源免費的適用于各種移動端的觸摸滑動插件??戳艘槐槲臋n,發(fā)現(xiàn)并不是很難,于是打算動手自己寫一個Swiper官網(wǎng)上的稍復(fù)雜點的小d...
摘要:最近在讀語言精粹這本書,作者是是一名來自的資深架構(gòu)師,以創(chuàng)建和維護格式而為大家所熟知。三元運算符有三個運算數(shù)。嘗試從的成員屬性中取值將會導(dǎo)致異常。這個過程稱為委托。通過可取得它們所屬對象的上下文的方法稱為公共方法。 最近在讀《JavaScript語言精粹》這本書,作者是 Douglas Crockford;Douglas Crockford是一名來自 Yahoo!的資深JavaScri...
閱讀 806·2021-11-23 09:51
閱讀 870·2021-11-23 09:51
閱讀 2535·2021-11-15 18:01
閱讀 3905·2021-10-11 11:07
閱讀 2434·2021-09-22 15:30
閱讀 1102·2021-09-22 14:59
閱讀 1582·2019-08-30 15:55
閱讀 1775·2019-08-30 15:52