瞭解JavaScript設計模式我們需要知道的一些必要知識點:(內容相對基礎,高手請跳過)
閉包:關於閉包這個月在園子裏有幾篇不錯的分享了,在這我也從最實際的地方出發,說說我的理解。
1.閉包最常用的方式就是返回一個內聯函數(何爲內聯函數?就是在函數內部聲明的函數);
2.在JavaScript中有作用域和執行環境的問題,在函數內部的變量在函數外部是無法訪問的,在函數內部卻可以得到全局變量。由於種種原因,我們有時候需要得到函數內部的變量,可是用常規方法是得不到的,這時我們就可以創建一個閉包,用來在外部訪問這個變量。
3.閉包的用途 主要就是上一點提到的讀取函數內部變量,還有一個作用就是可以使這些變量一直保存在內存中。
4.使用閉包要注意,由於變量被保存在內存中,所以會對內存造成消耗,所以不能濫用閉包。解決方法是 在退出函數之前,將不使用的局部變量全部刪除。
最後還是上一套閉包的代碼吧,這樣更直觀。
1 function f(){ 2 var n = 999; 3 function f1(){ 4 alert(n+=1); 5 } 6 return f1; 7 } 8 var result = f(); 9 result(); // 1000 10 result(); // 1001 11 result(); // 1002
封裝:通過將一個方法或者屬性聲明爲私用的,可以讓對象的實現細節對其他對象保密以降低對象之間的耦合程度,可以保持數據的完整性並對其修改方式加以約束,這樣可以是代碼更可靠,更易於調試。封裝是面向對象的設計的基石。
儘管JavaScript是一門面向對象的語言,可它並不具備將成員聲明爲公用或私用的任何內部機制,所以我們只能自己想辦法實現這種特性。下面還是通過一套完整的代碼去分析,介紹什麼是私有屬性和方法,什麼是特權屬性和方法,什麼是公有屬性和方法,什麼是公有靜態屬性和方法。
私有屬性和方法:函數有作用域,在函數內用var 關鍵字聲明的變量在外部無法訪問,私有屬性和方法本質就是你希望在對象外部無法訪問的變量。
特權屬性和方法:創建屬性和方法時使用的this關鍵字,因爲這些方法定義在構造器的作用域中,所以它們可以訪問到私有屬性和方法;只有那些需要直接訪問私有成員的方法才應該被設計爲特權方法。
共有屬性和方法:直接鏈在prototype上的屬性和方法,不可以訪問構造器內的私有成員,可以訪問特權成員,子類會繼承所有的共有方法。
共有靜態屬性和方法:最好的理解方式就是把它想象成一個命名空間,實際上相當於把構造器作爲命名空間來使用。
1 /* -- 封裝 -- */ 2 var _packaging =function(){ 3 //私有屬性和方法 4 var name ='Darren'; 5 var method1 =function(){ 6 //... 7 } 8 //特權屬性和方法 9 this.title ='JavaScript Design Patterns' ; 10 this.getName =function(){ 11 return name; 12 } 13 } 14 //共有靜態屬性和方法 15 _packaging._name ='Darren code'; 16 _packaging.alertName =function(){ 17 alert(_packaging._name); 18 } 19 //共有屬性和方法 20 _packaging.prototype = { 21 init:function(){ 22 //... 23 } 24 }
繼承:繼承本身就是一個抽象的話題,在JavaScript中繼承更是一個複雜的話題,因爲JavaScript想要實現繼承有兩種實現方式,分別是類式繼承和原型式繼承,每種實現的方式都需要採取不少措施,下面本人通過分析例子的方式講解JavaScript中這個很重要的話題。
1 /* -- 類式繼承 -- */ 2 //先聲明一個超類 3 function Person(name){ 4 this.name = name; 5 } 6 //給這個超類的原型對象上添加方法 getName 7 Person.prototype.getName =function(){ 8 returnthis.name; 9 } 10 //實例化這個超類 11 var a =new Person('Darren1') 12 alert(a.getName()); 13 //再聲明類 14 function Programmer(name,sex){ 15 //這個類中要調用超類Person的構造函數,並將參數name傳給它 16 Person.call(this,name); 17 this.sex = sex; 18 } 19 //這個子類的原型對象等於超類的實例 20 Programmer.prototype =new Person(); 21 //因爲子類的原型對象等於超類的實例,所以prototype.constructor這個方法也等於超類構造函數,你可以自己測試一下,如果沒這一步,alert(Programmer.prototype.constructor),這個是Person超類的引用,所以要從新賦值爲自己本身 22 Programmer.prototype.constructor = Programmer; 23 //子類本身添加了getSex 方法 24 Programmer.prototype.getSex =function(){ 25 returnthis.sex; 26 } 27 //實例化這個子類 28 var _m =new Programmer('Darren2','male'); 29 //自身的方法 30 alert(_m.getSex()); 31 //繼承超類的方法 32 alert(_m.getName());
代碼都不難,只要對 原型鏈 有基礎就能理解。類式繼承模式是JavaScript繼承主要的模式,幾乎所有用面向對象方式編寫的JavaScript代碼中都用到了這種繼承,又因爲在各種流行語言中只有JavaScript使用原型式繼承,因此最好還是使用類式繼承。可是要熟悉JavaScript語言,原型繼承也是我們必須所瞭解的,至於在項目中是否使用就得看個人編碼風格了。
1 /* -- 原型式繼承 -- */ 2 //clone()函數用來創建新的類Person對象 3 var clone =function(obj){ 4 var _f =function(){}; 5 //這句是原型式繼承最核心的地方,函數的原型對象爲對象字面量 6 _f.prototype = obj; 7 returnnew _f; 8 } 9 //先聲明一個對象字面量 10 var Person = { 11 name:'Darren', 12 getName:function(){ 13 returnthis.name; 14 } 15 } 16 //不需要定義一個Person的子類,只要執行一次克隆即可 17 var Programmer = clone(Person); 18 //可以直接獲得Person提供的默認值,也可以添加或者修改屬性和方法 19 alert(Programmer.getName()) 20 Programmer.name ='Darren2' 21 alert(Programmer.getName()) 22 23 //聲明子類,執行一次克隆即可 24 var Someone = clone(Programmer);
------------------------------------------ 正文開始了,我是分割線 ------------------------------------------
前言:
JavaScript設計模式的作用 - 提高代碼的重用性,可讀性,使代碼更容易的維護和擴展。
1.單體模式,工廠模式,橋樑模式個人認爲這個一個優秀前端必須掌握的模式,對抽象編程和接口編程都非常有好處。
2.裝飾者模式和組合模式有很多相似的地方,它們都與所包裝的對象實現同樣的接口並且會把任何方法的調用傳遞給這些對象。裝飾者模式和組合模式是本人描述的較吃力的兩個模式,我個人其實也沒用過,所以查了很多相關資料和文檔,請大家海涵。
3.門面模式是個非常有意思的模式,幾乎所有的JavaScript庫都會用到這個模式,假如你有逆向思維或者逆向編程的經驗,你會更容易理解這個模式(聽起來有挑戰,其實一接觸你就知道這是個很簡單的模式);還有配置器模式得和門面模式一塊拿來說,這個模式對現有接口進行包裝,合理運用可以很多程度上提高開發效率。這兩個模式有相似的地方,所以一塊理解的話相信都會很快上手的。
4.享元模式是一種以優化爲目的的模式。
5.代理模式主要用於控制對象的訪問,包括推遲對其創建需要耗用大量計算資源的類得實例化。
6.觀察者模式用於對對象的狀態進行觀察,並且當它發生變化時能得到通知的方法。用於讓對象對事件進行監聽以便對其作出響應。觀察者模式也被稱爲“訂閱者模式”。
7.命令模式是對方法調用進行封裝的方式,用命名模式可以對方法調用進行參數化和傳遞,然後在需要的時候再加以執行。
8.職責鏈模式用來消除請求的發送者和接收者之間的耦合。
JavaScript設計模式都有哪些?
單體(Singleton)模式: 絕對是JavaScript中最基本最有用的模式。
單體在JavaScript的有多種用途,它用來劃分命名空間。可以減少網頁中全局變量的數量(在網頁中使用全局變量有風險);可以在多人開發時避免代碼的衝突(使用合理的命名空間)等等。
在中小型項目或者功能中,單體可以用作命名空間把自己的代碼組織在一個全局變量名下;在稍大或者複雜的功能中,單體可以用來把相關代碼組織在一起以便日後好維護。
使用單體的方法就是用一個命名空間包含自己的所有代碼的全局對象,示例:
1 var functionGroup = { 2 name:'Darren', 3 method1:function(){ 4 //code 5 }, 6 init:function(){ 7 //code 8 } 9 }
或者
1 var functionGroup =newfunction myGroup(){ 2 this.name ='Darren'; 3 this.getName =function(){ 4 returnthis.name 5 } 6 this.method1 =function(){} 7 ... 8 }
工廠(Factory)模式:提供一個創建一系列相關或相互依賴對象的接口,而無需指定他們具體的類。
工廠就是把成員對象的創建工作轉交給一個外部對象,好處在於消除對象之間的耦合(何爲耦合?就是相互影響)。通過使用工廠方法而不是new關鍵字及具體類,可以把所有實例化的代碼都集中在一個位置,有助於創建模塊化的代碼,這纔是工廠模式的目的和優勢。
舉個例子:你有一個大的功能要做,其中有一部分是要考慮擴展性的,那麼這部分代碼就可以考慮抽象出來,當做一個全新的對象做處理。好處就是將來擴展的時候容易維護 - 只需要操作這個對象內部方法和屬性,達到了動態實現的目的。非常有名的一個示例 - XHR工廠:
1 var XMLHttpFactory =function(){}; //這是一個簡單工廠模式 2 XMLHttpFactory.createXMLHttp =function(){ 3 var XMLHttp = null; 4 if (window.XMLHttpRequest){ 5 XMLHttp = new XMLHttpRequest() 6 }elseif (window.ActiveXObject){ 7 XMLHttp = new ActiveXObject("Microsoft.XMLHTTP") 8 } 10 return XMLHttp; 11 } 12 //XMLHttpFactory.createXMLHttp()這個方法根據當前環境的具體情況返回一個XHR對象。 13 var AjaxHander =function(){ 14 var XMLHttp = XMLHttpFactory.createXMLHttp(); 15 ... 16 }
工廠模式又區分簡單工廠模式和抽象工廠模式,上面介紹的是簡單工廠模式,這種模式用的更多也更簡單易用。抽象工廠模式的使用方法就是 - 先設計一個抽象類,這個類不能被實例化,只能用來派生子類,最後通過對子類的擴展實現工廠方法。 示例:
1 var XMLHttpFactory =function(){}; //這是一個抽象工廠模式 2 XMLHttpFactory.prototype = { 3 //如果真的要調用這個方法會拋出一個錯誤,它不能被實例化,只能用來派生子類 4 createFactory:function(){ 5 thrownew Error('This is an abstract class'); 6 } 7 } 8 //派生子類,文章開始處有基礎介紹那有講解繼承的模式,不明白可以去參考原理 9 var XHRHandler =function(){ 10 XMLHttpFactory.call(this); 11 }; 12 XHRHandler.prototype =new XMLHttpFactory(); 13 XHRHandler.prototype.constructor = XHRHandler; 14 //重新定義createFactory 方法 15 XHRHandler.prototype.createFactory =function(){ 16 var XMLHttp =null; 17 if (window.XMLHttpRequest){ 18 XMLHttp =new XMLHttpRequest() 19 }elseif (window.ActiveXObject){ 20 XMLHttp =new ActiveXObject("Microsoft.XMLHTTP") 21 } 22 return XMLHttp; 23 }
橋接(bridge)模式:在實現API的時候,橋樑模式灰常有用。在所有模式中,這種模式最容易立即付諸實施。
橋樑模式可以用來弱化它與使用它的類和對象之間的耦合,就是將抽象與其實現隔離開來,以便二者獨立變化;這種模式對於JavaScript中常見的時間驅動的編程有很大益處,橋樑模式最常見和實際的應用場合之一是時間監聽器回調函數。先分析一個不好的示例:
1 element.onclick =function(){ 2 new setLogFunc(); 3 };
爲什麼說這個示例不好,因爲從這段代碼中無法看出那個LogFunc方法要顯示在什麼地方,它有什麼可配置的選項以及應該怎麼去修改它。換一種說法就是,橋樑模式的要訣就是讓接口“可橋樑”,實際上也就是可配置。把頁面中一個個功能都想象成模塊,接口可以使得模塊之間的耦合降低。
掌握橋樑模式的正確使用收益的不只是你,還有那些負責維護你代碼的人。把抽象於其實現隔離開,可獨立地管理軟件的各個部分,bug也因此更容易查找。
橋樑模式目的就是讓API更加健壯,提高組件的模塊化程度,促成更簡潔的實現,並提高抽象的靈活性。一個好的示例:
1 element.onclick =function(){ //API可控制性提高了,使得這個API更加健壯 2 new someFunction(element,param,callback); 3 }
注:橋樑模式還可以用於連接公開的API代碼和私有的實現代碼,還可以把多個類連接在一起。在文章封裝介紹的部分提到過特權方法,也是橋樑模式的一種特例。《JS設計模式》上找的示例,加深大家對這個模式的理解:
1 //錯誤的方式 2 //這個API根據事件監聽器回調函數的工作機制,事件對象被作爲參數傳遞給這個函數。本例中並沒有使用這個參數,而只是從this對象獲取ID。 3 addEvent(element,'click',getBeerById); 4 function(e){ 5 var id =this.id; 6 asyncRequest('GET','beer.url?id='+ id,function(resp){ 7 //Callback response 8 console.log('Requested Beer: '+ resp.responseText); 9 }); 10 } 11 12 //好的方式 13 //從邏輯上分析,把id傳給getBeerById函數式合情理的,且迴應結果總是通過一個毀掉函數返回。這麼理解,我們現在做的是針對接口而不是實現進行編程,用橋樑模式把抽象隔離開來。 14 function getBeerById(id,callback){ 15 asyncRequest('GET','beer.url?id='+ id,function(resp){ 16 callback(resp.responseText) 17 }); 18 } 19 addEvent(element,'click',getBeerByIdBridge); 20 function getBeerByIdBridge(e){ 21 getBeerById(this.id,function(beer){ 22 console.log('Requested Beer: '+ beer); 23 }); 24 }
裝飾者(Decorator)模式:這個模式就是爲對象增加功能(或方法)。
動態地給一個對象添加一些額外的職責。就擴展功能而言,它比生成子類方式更爲靈活。
裝飾者模式和組合模式有很多共同點,它們都與所包裝的對象實現統一的接口並且會把任何方法條用傳遞給這些對象。可是組合模式用於把衆多子對象組織爲一個整體,而裝飾者模式用於在不修改現有對象或從派生子類的前提下爲其添加方法。
裝飾者的運作過程是透明的,這就是說你可以用它包裝其他對象,然後繼續按之前使用那麼對象的方法來使用,從下面的例子中就可以看出。還是從代碼中理解吧:
1 //創建一個命名空間爲myText.Decorations 2 var myText= {}; 3 myText.Decorations={}; 4 myText.Core=function(myString){ 5 this.show =function(){return myString;} 6 } 7 //第一次裝飾 8 myText.Decorations.addQuestuibMark =function(myString){ 9 this.show =function(){return myString.show()+'?';}; 10 } 11 //第二次裝飾 12 myText.Decorations.makeItalic =function(myString){ 13 this.show =function(){return'<li>'+myString.show()+'</li>'}; 14 } 15 //得到myText.Core的實例 16 var theString =new myText.Core('this is a sample test String'); 17 alert(theString.show()); //output 'this is a sample test String' 18 theString =new myText.Decorations.addQuestuibMark(theString); 19 alert(theString.show()); //output 'this is a sample test String?' 20 theString =new myText.Decorations.makeItalic (theString); 21 alert(theString.show()); //output '<li>this is a sample test String</li>'
從這個示例中可以看出,這一切都可以不用事先知道組件對象的接口,甚至可以動態的實現,在爲現有對象增添特性這方面,裝飾者模式有極大的靈活性。
如果需要爲類增加特性或者方法,而從該類派生子類的解決辦法並不實際的話,就應該使用裝飾者模式。派生子類之所以會不實際最常見的原因是需要添加的特性或方法的數量要求使用大量子類。
組合(Composite)模式:將對象組合成樹形結構以表示“部分-整體”的層次結構。它使得客戶對單個對象和複合對象的使用具有一致性。
組合模式是一種專爲創建Web上的動態用戶界面而量身定製的模式。使用這種模式,可以用一條命令在多個對象上激發複雜的或遞歸的行爲。組合模式擅長於對大批對象進行操作。
組合模式的好處:1.程序員可以用同樣的方法處理對象的集合與其中的特定子對象;2.它可以用來把一批子對象組織成樹形結構,並且使整棵樹都可被便利。
組合模式適用範圍:1.存在一批組織成某處層次體系的對象(具體結構可能在開發期間無法知道);2.希望對這批對象或其中的一部分對象實話一個操作。
其實組合模式就是將一系列相似或相近的對象組合在一個大的對象,由這個大對象提供一些常用的接口來對這些小對象進行操作,代碼可重用,對外操作簡單。例如:對form內的元素,不考慮頁面設計的情況下,一般就剩下input了,對於這些input都有name和value的屬性,因此可以將這些input元素作爲form對象的成員組合起來,form對象提供對外的接口,便可以實現一些簡單的操作,比如設置某個input的value,添加/刪除某個input等等。
這種模式描述起來比較吃力,我從《JS設計模式》上找個一個實例,大家還是看代碼吧:先創建組合對象類
1 // DynamicGallery Class 2 var DynamicGallery =function (id) { // 實現Composite,GalleryItem組合對象類 3 this.children = []; 4 this.element = document.createElement('div'); 5 this.element.id = id; 6 this.element.className ='dynamic-gallery'; 7 } 8 DynamicGallery.prototype = { 9 // 實現Composite組合對象接口 10 add: function (child) { 11 this.children.push(child); 12 this.element.appendChild(child.getElement()); 13 }, 14 remove: function (child) { 15 for (var node, i =0; node =this.getChild(i); i++) { 16 if (node == child) { 17 this.children.splice(i, 1); 18 break; 19 } 20 } 21 this.element.removeChild(child.getElement()); 22 }, 23 getChild: function (i) { 24 returnthis.children[i]; 25 }, 26 // 實現DynamicGallery組合對象接口 27 hide: function () { 28 for (var node, i =0; node =this.getChild(i); i++) { 29 node.hide(); 30 } 31 this.element.style.display ='none'; 32 }, 33 show: function () { 34 this.element.style.display ='block'; 35 for (var node, i =0; node = getChild(i); i++) { 36 node.show(); 37 } 38 }, 39 // 幫助方法 40 getElement: function () { 41 returnthis.element; 42 } 43 }
再創建葉對象類
1 var GalleryImage =function (src) { // 實現Composite和GalleryItem組合對象中所定義的方法 2 this.element = document.createElement('img'); 3 this.element.className ='gallery-image'; 4 this.element.src = src; 5 } 6 GalleryImage.prototype = { 7 // 實現Composite接口 8 // 這些是葉結點,所以我們不用實現這些方法,我們只需要定義即可 9 add: function () { }, 10 remove: function () { }, 11 getChild: function () { }, 12 // 實現GalleryItem接口 13 hide: function () { 14 this.element.style.display ='none'; 15 }, 16 show: function () { 17 this.element.style.display =''; 18 }, 19 // 幫助方法 20 getElement: function () { 21 returnthis.element; 22 } 23 }
現在我們可以使用這兩個類來管理圖片:
1 var topGallery =new DynamicGallery('top-gallery'); 2 topGallery.add(new GalleryImage('/img/image-1.jpg')); 3 topGallery.add(new GalleryImage('/img/image-2.jpg')); 4 topGallery.add(new GalleryImage('/img/image-3.jpg')); 5 var vacationPhotos =new DyamicGallery('vacation-photos'); 6 for(var i =0, i <30; i++){ 7 vacationPhotos.add(new GalleryImage('/img/vac/image-'+ i +'.jpg')); 8 } 9 topGallery.add(vacationPhotos); 10 topGallery.show(); 11 vacationPhotos.hide();
門面(facade)模式:門面模式是幾乎所有JavaScript庫的核心原則
子系統中的一組接口提供一個一致的界面,門面模式定義了一個高層接口,這個接口使得這一子系統更加容易使用,簡單的說這是一種組織性的模式,它可以用來修改類和對象的接口,使其更便於使用。
門面模式的兩個作用:1.簡化類的接口;2.消除類與使用它的客戶代碼之間的耦合。
門面模式的使用目的就是圖方面。
想象一下計算機桌面上的那些快捷方式圖標,它們就是在扮演一個把用戶引導至某個地方的接口的角色,每次操作都是間接的執行一些幕後的命令。
你在看這篇的博客的時候我就假設你已經有JavaScript的使用經驗了,那麼你一定寫過或者看過這樣的代碼:
1 var addEvent =function(el,type,fn){ 2 if(window.addEventListener){ 3 el.addEventListener(type,fn); 4 }elseif(window.attachEvent){ 5 el.attachEvent('on'+type,fn); 6 }else{ 7 el['on'+type] = fn; 8 } 9 }
這個就是一個JavaScript中常見的事件監聽器函數,這個函數就是一個基本的門面,有了它,就有了爲DOM節點添加事件監聽器的簡便方法。
現在要說門面模式的精華部分了,爲什麼說JavaScript庫幾乎都會用這種模式類。假如現在要設計一個庫,那麼最好把其中所有的工具元素放在一起,這樣更好用,訪問起來更簡便。看代碼:
1 //_model.util是一個命名空間 2 _myModel.util.Event = { 3 getEvent:function(e){ 4 return e|| window.event; 5 }, 6 getTarget:function(e){ 7 return e.target||e.srcElement; 8 }, 9 preventDefault:function(e){ 10 if(e.preventDefault){ 11 e.preventDefault(); 12 }else{ 13 e.returnValue =false; 14 } 15 } 16 }; 17 //事件工具大概就是這麼一個套路,然後結合addEvent函數使用 18 addEvent(document.getElementsByTagName('body')[0],'click',function(e){ 19 alert(_myModel.util.Event.getTarget(e)); 20 });
個人認爲,在處理遊覽器差異問題時最好的解決辦法就是把這些差異抽取的門面方法中,這樣可以提供一個更一致的接口,addEvent函數就是一個例子。
適配置器(Adapter)模式:將一個類的接口轉換成客戶希望的另外一個接口。適配器模式使得原本由於接口不兼容而不能一起工作的那些類可以一起工作,使用這種模式的對象又叫包裝器,因爲他們是在用一個新的接口包裝另一個對象。
從表面上看,它和門面模式有點相似,差別在於它們如何改變接口,門面模式展現的是一個簡化的接口,它並不提供額外的選擇,而適配器模式則要把一個接口轉換爲另一個接口,它並不會濾除某些能力,也不會簡化接口。先來一個簡單的示例看看:
1 //假如有一個3個字符串參數的函數,但是現在擁有的卻是一個包含三個字符串元素的對象,那麼就可以用一個配置器來銜接二者 2 var clientObject = { 3 str1:'bat', 4 str2:'foo', 5 str3:'baz' 6 } 7 function interfaceMethod(str1,str2,str3){ 8 alert(str1) 9 } 10 //配置器函數 11 function adapterMethod(o){ 12 interfaceMethod(o.str1, o.str2, o.str3); 13 } 14 adapterMethod(clientObject) 15 //adapterMethod函數的作爲就在於對interfaceMethod函數進行包裝,並把傳遞給它的參數轉換爲後者需要的形式。
適配器模式的工作機制是:用一個新的接口對現有類得接口進行包裝。
示例:適配兩個庫。下面的例子要實現的是從Prototype庫的$函數到YUI的get方法的轉換。
1 //先看它們在接口方面的差別 2 //Prototype $ function 3 function $(){ 4 var elements =new Array(); 5 for(var i=0;i<arguments.length;i++){ 6 var element = arguments[i]; 7 if(typeof element =='string'){ 8 element = document.getElementById(element); 9 } 10 if(typeof.length ==1) return element; 11 elements.push(element); 12 } 13 return elements; 14 } 15 //YUI get method 16 YAHOO.util.Dom.get =function(el){ 17 if(YAHOO.lang.isString(el)){ 18 return document.getElementById(el); 19 } 20 if(YAHOO.lang.isArray(el)){ 21 var c =[]; 22 for(var i=0,len=el.length;i<len;++i){ 23 c[c.length] = YAHOO.util.Dom.get(el[i]); 24 } 25 return c; 26 } 27 if(el){ 28 return el; 29 } 30 returnnull; 31 } 32 //二者區別就在於get具有一個參數,且可以是HTML,字符串或者數組;而$木有正是的參數,允許使用者傳入任意數目的參數,不管HTML還是字符串。 33 //如果需要從使用Prototype的$函數改爲使用YUI的get方法(或者相反,那麼用適配器模式其實很簡單) 34 function PrototypeToYUIAdapter(){ 35 return YAHOO.util.Dom.get(arguments); 36 } 37 function YUIToPrototypeAdapter(el){ 38 return $.apply(window,el instanceof Array?el:[el]); 39 }
享元(Flyweight)模式:運用共享技術有效地支持大量細粒度的對象。
享元模式可以避免大量非常相似類的開銷。在程序設計中有時需要生成大量細粒度的類實例來表示數據。如果發現這些實例除了幾個參數外基本傷都是相同的,有時就能夠受大幅度第減少需要實例化的類的數量。如果能把這些參數移到類實例外面,在方法調用時將他們傳遞進來,就可以通過共享大幅度地減少單個實例的數目。
從實際出發說說自己的理解吧。
1 組成部分 2 “享元”:抽離出來的外部操作和數據; 3 “工廠”:創造對象的工廠; 4 “存儲器”:存儲實例對象的對象或數組,供“享元”來統一控制和管理。 5 6 應用場景 7 1. 頁面存在大量資源密集型對象; 8 2. 這些對象具備一定的共性,可以抽離出公用的操作和數據 9 10 關鍵 11 1. 合理劃分內部和外部數據。 12 既要保持每個對象的模塊性、保證享元的獨立、可維護,又要儘可能多的抽離外部數據。 13 2. 管理所有實例 14 既然抽離出了外部數據和操作,那享元就必須可以訪問和控制實例對象。在JavaScript這種動態語言中,這個需求是很容易實現的:我們可以把工廠生產出的對象簡單的扔在一個數組中。爲每個對象設計暴露給外部的方法,便於享元的控制。 15 16 優點 17 1. 將能耗大的操作抽離成一個,在資源密集型系統中,可大大減少資源和內存佔用; 18 2. 職責封裝,這些操作獨立修改和維護; 19 20 缺點 21 1. 增加了實現複雜度。 22 將原本由一個工廠方法實現的功能,修改爲了一個享元+一個工廠+一個存儲器。 23 2. 對象數量少的情況,可能會增大系統開銷。
示例:
1 //汽車登記示例 2 var Car =function(make,model,year,owner,tag,renewDate){ 3 this.make=make; 4 this.model=model; 5 this.year=year; 6 this.owner=owner; 7 this.tag=tag; 8 this.renewDate=renewDate; 9 } 10 Car.prototype = { 11 getMake:function(){ 12 returnthis.make; 13 }, 14 getModel:function(){ 15 returnthis.model; 16 }, 17 getYear:function(){ 18 returnthis.year; 19 }, 20 transferOwner:function(owner,tag,renewDate){ 21 this.owner=owner; 22 this.tag=tag; 23 this.renewDate=renewDate; 24 }, 25 renewRegistration:function(renewDate){ 26 this.renewDate=renewDate; 27 } 28 } 29 //數據量小到沒多大的影響,數據量大的時候對計算機內存會產生壓力,下面介紹享元模式優化後 30 //包含核心數據的Car類 31 var Car=function(make,model,year){ 32 this.make=make; 33 this.model=model; 34 this.year=year; 35 } 36 Car.prototype={ 37 getMake:function(){ 38 returnthis.make; 39 }, 40 getModel:function(){ 41 returnthis.model; 42 }, 43 getYear:function(){ 44 returnthis.year; 45 } 46 } 47 //中間對象,用來實例化Car類 48 var CarFactory=(function(){ 49 var createdCars = {}; 50 return { 51 createCar:function(make,model,year){ 52 var car=createdCars[make+"-"+model+"-"+year]; 53 return car ? car : createdCars[make +'-'+ model +'-'+ year] =(new Car(make,model,year)); 54 } 55 } 56 })(); 57 //數據工廠,用來處理Car的實例化和整合附加數據 58 var CarRecordManager = (function() { 59 var carRecordDatabase = {}; 60 return { 61 addCarRecord:function(make,model,year,owner,tag,renewDate){ 62 var car = CarFactory.createCar(make, model, year); 63 carRecordDatabase[tag]={ 64 owner:owner, 65 tag:tag, 66 renewDate:renewDate, 67 car:car 68 } 69 }, 70 transferOwnership:function(tag, newOwner, newTag, newRenewDate){ 71 var record=carRecordDatabase[tag]; 72 record.owner = newOwner; 73 record.tag = newTag; 74 record.renewDate = newRenewDate; 75 }, 76 renewRegistration:function(tag,newRenewDate){ 77 carRecordDatabase[tag].renewDate=newRenewDate; 78 }, 79 getCarInfo:function(tag){ 80 return carRecordDatabase[tag]; 81 } 82 } 83 })();
代理(Proxy)模式:此模式最基本的形式是對訪問進行控制。代理對象和另一個對象(本體)實現的是同樣的接口,可是實際上工作還是本體在做,它纔是負責執行所分派的任務的那個對象或類,代理對象不會在另以對象的基礎上修改任何方法,也不會簡化那個對象的接口。
舉一個具體的情況:如果那個對象在某個遠端服務器上,直接操作這個對象因爲網絡速度原因可能比較慢,那我們可以先用Proxy來代替那個對象。
總之對於開銷較大的對象,只有在使用它時才創建,這個原則可以爲我們節省很多內存。《JS設計模式》上的圖書館示例:
1 var Publication =new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle', 'setTitle', 'getAuthor', 'setAuthor', 'display']); 2 var Book =function(isbn, title, author) { 3 //... 4 } 5 // implements Publication 6 implements(Book,Publication); 7 8 /* Library interface. */ 9 var Library =new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']); 10 11 /* PublicLibrary class. */ 12 var PublicLibrary =function(books) { 13 //... 14 }; 15 // implements Library 16 implements(PublicLibrary,Library); 17 18 PublicLibrary.prototype = { 19 findBooks: function(searchString) { 20 //... 21 }, 22 checkoutBook: function(book) { 23 //... 24 }, 25 returnBook: function(book) { 26 //... 27 } 28 }; 29 30 /* PublicLibraryProxy class, a useless proxy. */ 31 var PublicLibraryProxy =function(catalog) { 32 this.library =new PublicLibrary(catalog); 33 }; 34 // implements Library 35 implements(PublicLibraryProxy,Library); 36 37 PublicLibraryProxy.prototype = { 38 findBooks: function(searchString) { 39 returnthis.library.findBooks(searchString); 40 }, 41 checkoutBook: function(book) { 42 returnthis.library.checkoutBook(book); 43 }, 44 returnBook: function(book) { 45 returnthis.library.returnBook(book); 46 } 47 };
觀察者(Observer)模式:定義對象間的一種一對多的依賴關係,以便當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並自動刷新。
觀察者模式中存在兩個角色,觀察者和被觀察者。在DOM的編程環境中的高級事件模式中,事件監聽器說到底就是一種內置的觀察者。事件處理器(handler)和時間監聽器(listener)並不是一回事,前者就是一種把事件傳給與其關聯的函數的手段,而在後者中,一個時間可以與幾個監聽器關聯,每個監聽器都能獨立於其他監聽器而改變。
1 //使用時間監聽器可以讓多個函數相應一個事件 2 var fn1 =function(){ 3 //code 4 } 5 var fn2 =function(){ 6 //code 7 } 8 addEvent(element,'click',fn1); 9 addEvent(element,'click',fn2) 10 11 //而時間處理函數就辦不到 12 element.onclick = fn1; 13 element.onclick = fn2;
觀察者模式是開發基於行爲的應用程序的有力手段,前端程序員可做的就是藉助一個事件監聽器替你處理各種行爲,從而降低內存消耗和提高互動性能。
命令(Command)模式:將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可取消的操作。
命令對象是一個操作和用來調用這個操作的對象的結合體,所有的命名對象都有一個執行操作,其用途就是調用命令對象所綁定的操作。示例:
1 car Calculator={ 2 add:function(x,y){ 3 return x+y; 4 }, 5 substract:function(x,y){ 6 return x-y; 7 }, 8 multiply:function(x,y){ 9 return x*y; 10 }, 11 divide:function(x,y){ 12 return x/y; 13 } 14 } 15 Calculator.calc =function(command){ 16 return Calculator[command.type](command.op1,command.opd2) 17 }; 18 Calculator.calc({type:'add',op1:1,op2:1}); 19 Calculator.calc({type:'substract',op1:5,op2:2}); 20 Calculator.calc({type:'multiply',op1:5,op2:2}); 21 Calculator.calc({type:'divide',op1:8,op2:4});
命名模式的主要用途是把調用對象(用戶界面,API和代理等)與實現操作的對象隔離開,也就是說使對象間的互動方式需要更高的模塊化時都可以用到這種模式。
職責鏈(Chain Of Responsibility)模式:爲解除請求的發送者和接收者之間耦合,而使多個對象都有機會處理這個請求。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它。
職責鏈由多個不同類型的對象組成:發送者是發出請求的對象,而接收者則是接收請求並且對其進行處理或傳遞的對象,請求本身有時也是一個對象,它封裝着與操作有關的所有數據。
典型的流程大致是:
1.發送者知道鏈中第一個接收者,它向這個接收者發出請求。
2.每一個接收者都對請求進行分析,然後要麼處理它,要麼將其往下傳。
3.每一個接收者知道的其他對象只有一個,即它在鏈中的下家。
4.如果沒有任何接收者處理請求,那麼請求將從鏈上離開,不同的實現對此也有不同的反應,一般會拋出一個錯誤。
職責鏈模式的適用範圍:1.有多個的對象可以處理一個請求,哪個對象處理該請求運行時刻自動確定;2.想在不明確指定接收者的情況下,向多個對象中的一個提交一個請求;3.可處理一個請求的對象集合需要被動態指定。
確實對這種模式不瞭解,相關資料也較少,所以代碼先不上了。看看大家對這個模式有木有什麼好的理解或者能較好表達這種模式的代碼,謝謝了。