深入理解javascript之設計模式
設計模式
設計模式是命名、抽象和識別對可重用的面向對象設計實用的的通用設計結構。
設計模式確定類和他們的實體、他們的角色和協作、還有他們的責任分配。
每個設計模式都聚焦於一個面向對象的設計難題或問題。
它描寫敘述了在其他設計的約束下它是否能使用。使用它後的後果和得失。
由於我們必須終於實現我們的設計模式,所以每個設計模式都提供了樣例,代碼來對實現進行闡釋.
盡管設計模式被描寫敘述為面向對象的設計,它們基於那些已經被主流面向對象語言實現過的解決方式...”。
種類
設計模式能夠被分成幾個不同的種類。
在這個部分我們將分為三類:創建型設計模式、結構設計模式、行為設計模式。
創建型設計模式
創建型設計模式
屬於這一類的一些模式是:構造器模式(Constructor),工廠模式(Factory),抽象工廠模式(Abstract),原型模式(Prototype),單例模式(Singleton)以及 建造者模式(Builder)。
結構設計模式
結構模式關註於對象組成和通常識別的方式實現不同對象之間的關系。該模式有助於在系統的某一部分發生改變的時候,整個系統結構不須要改變。該模式相同有助於對系統中某部分沒有達到某一目的的部分進行重組。
在該分類下的模式有:裝飾模式,外觀模式,享元模式。適配器模式和代理模式。
行為設計模式
行為模式關註改善或精簡在系統中不同對象間通信。
行為模式包含:叠代模式。中介者模式,觀察者模式和訪問者模式。
以下我們通過分開介紹各個經常使用的設計模式,來加深對設計模式的理解。
構造器模式
構造器是一個當新建對象的內存被分配後,用來初始化該對象的一個特殊函數。對象構造器是被用來創建特殊類型的對象的,首先它要準備使用的對象。其次在對象初次被創建時,通過接收參數。構造器要用來對成員的屬性和方法進行賦值。
因為javascript不支持類的概念,所以必須通過構造器來使用newkeyword初始化對象。
一個基礎的構造器代碼例如以下:
function Car( model, year, miles ) { this.model = model; this.year = year; this.miles = miles; this.toString = function () { return this.model + " has done " + this.miles + " miles"; }; } // 使用: // 我們能夠演示樣例化一個Car var civic = new Car( "Honda Civic", 2009, 20000 ); var mondeo = new Car( "Ford Mondeo", 2010, 5000 ); // 打開瀏覽器控制臺查看這些對象toString()方法的輸出值 // output of the toString() method being called on // these objects console.log( civic.toString() ); console.log( mondeo.toString() );
可是這種話,繼承起來比較麻煩,並且每一個Car構造函數創建的對象中,toString之類的函數都會被又一次定義。所以還是要利用原型,來實現最佳的構造器:
function Car( model, year, miles ) { this.model = model; this.year = year; this.miles = miles; } // 註意這裏我們使用Note here that we are using Object.prototype.newMethod 而不是 // Object.prototype 。以避免我們又一次定義原型對象 Car.prototype.toString = function () { return this.model + " has done " + this.miles + " miles"; }; // 使用: var civic = new Car( "Honda Civic", 2009, 20000 ); var mondeo = new Car( "Ford Mondeo", 2010, 5000 ); console.log( civic.toString() ); console.log( mondeo.toString() );
工廠模式
一個工廠能提供一個創建對象的公共接口。我們能夠在當中指定我們希望被創建的工廠對象的類型。說的簡單點。就像飲水機,要咖啡還是牛奶取決於你按哪個button。
簡單工廠模式在創建ajax對象的時候能夠體現出來,能夠通過jquery中的$.ajax方法來理解,也能夠通過我自己寫的一個ajax方法來理解。地址在:http://runjs.cn/code/j5dkikwu
ajax("test002.txt",{ type:"GET", data:{ name:"liuf", age:23 }, onsuccess:function(responseText,xhr){ document.getElementById("input").value=responseText; }, onfail:function(){ //document.write("fail"); } });
ajax實際上就是一個工廠方法,至於究竟是用get方法還是post方法,都由後面的代碼來決定。這就是前面所說的“一個工廠能提供一個創建對象的公共接口,我們能夠在當中指定我們希望被創建的工廠對象的類型”。
單例模式
單例模式之所以這麽叫。是由於它限制一個類僅僅能有一個實例化對象。經典的實現方式是。創建一個類,這個類包括一個方法,這種方法在沒有對象存在的情況下,將會創建一個新的實例對象。
假設對象存在,這種方法僅僅是返回這個對象的引用。可是javascript本來就是無類的,所以簡單地來說。就是沒有就創建,有就不創建直接用。
那麽我們看看現實中的案例吧。點擊一個button後出現一個遮罩層,這是一個經常使用的需求吧。代碼例如以下:
var createMask = function(){ return document,body.appendChild( document.createElement(div) ); } $( ‘button‘ ).click( function(){ var mask = createMask(); mask.show(); })
這樣寫就會出現一個問題,就是每次調用createMask都會創建一個新的div,盡管能夠在隱藏遮罩層時將其remove,可是這樣還是會帶來性能的損耗。那麽能夠做例如以下改進。就是在頁面一開始就創建div。代碼例如以下:
var mask = document.body.appendChild( document.createElement( ‘‘div‘ ) ); $( ‘‘button‘ ).click( function(){ mask.show(); } )
這樣確實能夠保證頁面僅僅會創建一個遮罩層,可是也有一個問題,就是假設用戶不須要用到這個div,豈不是白白創建了它。於是我們能夠借助一個變量來推斷是否已經創建過div,代碼例如以下:
var mask; var createMask = function(){ if ( mask ) return mask; else{ mask = document,body.appendChild( document.createElement(div) ); return mask; } }
這樣看起來不錯,可是mask作為一個全局變量。是否會造成汙染呢?所以最好的辦法例如以下:
var createMask = function(){ var mask; return function(){ return mask || ( mask = document.body.appendChild( document.createElement(‘div‘) ) ) } }()
這就是前面所說的“沒有就創建,有就不創建直接用”。沒錯,單例模式就是這麽簡單,設計模式事實上並不難。編程中我們事實上一直實用到,僅僅是自己沒有發現罷了。
橋接模式
橋接模式就是將實現部分和抽象部分分離開來。以便兩者能夠獨立的變化。在實現api的時候,橋接模式很經常使用。
我們以javascript的forEach方法為例:
forEach = function( ary, fn ){ for ( var i = 0, l = ary.length; i < l; i++ ){ var c = ary[ i ]; if ( fn.call( c, i, c ) === false ){ return false; } } }
能夠看到,forEach函數並不關心fn裏面的詳細實現. fn裏面的邏輯也不會被forEach函數的改寫影響.
使用代碼例如以下:
forEach( [1,2,3], function( i, n ){ alert ( n*2 ) } ) forEach( [1,2,3], function( i, n ){ alert ( n*3 ) } )
外觀模式
外觀模式是一種無處不在的模式,外觀模式提供一個高層接口,這個接口使得client或者子系統調用起來更加方法。
比方:
var getName = function(){ return ‘‘svenzeng" } var getSex = function(){ return ‘man‘ }
如今我要調用兩個方法,我就能夠使用一個更加高層的接口來調用:
var getUserInfo = function(){ var info = getName () + getSex (); return info; }
這樣就方便組裝,假設一開始就把兩個寫到一個函數中,那就不可以僅僅單獨調用當中一個了。
享元模式
享元模式是一個優化反復、緩慢和低效數據共享代碼的經典結構化解決方式。它的目標是以相關對象盡可能多的共享數據,來降低應用程序中內存的使用(比如:應用程序的配置、狀態等)。通俗的講,享元模式就是用來降低程序所需的對象個數。
舉一個樣例,網頁中的瀑布流。或者webqq的好友列表中。每次往下拉時。都會創建新的div。那麽假設有非常對div呢?瀏覽器豈不是卡死了?所以我們會想到一種辦法。就是把已經消失在視線外的div都刪除掉。這樣頁面就能夠保持一定數量的節點,可是頻繁的刪除和加入節點,又會帶來非常大的性能開銷。
這個時候就能夠用到享元模式了,享元模式能夠提供一些共享的對象以便反復利用。比方頁面中僅僅能顯示10個div,那始終出如今用戶視線中的這10個div就能夠寫成享元。
原理事實上非常easy, 把剛隱藏起來的div放到一個數組中, 當須要div的時候, 先從該數組中取, 假設數組中已經沒有了, 再又一次創建一個. 這個數組裏的div就是享元, 它們每個都能夠當作不論什麽用戶信息的載體.代碼例如以下:
var getDiv = (function(){ var created = []; var create = function(){ return document.body.appendChild( document.createElement( ‘div‘ ) ); } var get = function(){ if ( created.length ){ return created.shift(); }else{ return create(); } } /* 一個如果的事件,用來監聽剛消失在視線外的div,實際上能夠通過監聽滾動欄位置來實現 */ userInfoContainer.disappear(function( div ){ created.push( div ); }) })() var div = getDiv(); div.innerHTML = "${userinfo}";
適配器模式
適配器模式就是將一個類的接口轉換成客戶希望的另外一個接口。通俗一點的說。將像蘋果手機不能差在電腦機箱上,必須有一個轉換器。而這個轉換器就是適配器。
在程序裏適配器模式也經經常使用來適配2個接口, 比方你如今正在用一個自己定義的js庫. 裏面有個依據id獲取節點的方法$id(). 有天你認為jquery裏的$實現得更酷, 但你又不想讓你的project師去學習新的庫和語法. 那一個適配器就能讓你完畢這件事情.
$id = function( id ){ return jQuery( ‘#‘ + id )[0]; }
這樣就不用再一個一個的改動了。
代理模式
代理模式就是把對一個對象的訪問,交給還有一個代理對象來操作。
說得通俗一點,程序猿每天寫日報。日報最後會給總監批閱。可是假設全部人都直接發給總監。那總監就沒法工作了。
所以每一個人會把自己的日報發給自己的組長,再由組長轉發給總監。這個組長就是代理。
編程中用到代理模式的情況也不少,比方大量操作dom時,我們會先創建文檔碎片,再統一加到dom樹中。
示比例如以下:
中介者模式
中介者模式是觀察者模式中的共享被觀察者對象。
在這個系統中的對象之間直接的公布/訂閱關系被犧牲掉了。取而代之的是維護一個通信的中心節點。中介者模式和代理模式是有差別的。差別例如以下:
中介者對象能夠讓各個對象之間不須要相互引用。從而使其耦合松散,並且能夠獨立的改變透明之間的交互。
通俗點講,銀行在存款人和貸款人之間也能看成一個中介。存款人A並不關心他的錢最後被誰借走。
貸款人B也不關心他借來的錢來自誰的存款。由於有中介的存在。這場交易才變得如此方便。
在編程中,大名鼎鼎的MVC結構中的Controller不就是一個中介者嗎?拿backbone舉例. 一個mode裏的數據並不確定最後被哪些view使用. view須要的數據也能夠來自隨意一個mode. 全部的綁定關系都是在controler裏決定. 中介者把復雜的多對多關系, 變成了2個相對簡單的1對多關系。
演示樣例代碼例如以下:
var mode1 = Mode.create(), mode2 = Mode.create(); var view1 = View.create(), view2 = View.create(); var controler1 = Controler.create( mode1, view1, function(){ view1.el.find( ‘‘div‘ ).bind( ‘‘click‘, function(){ this.innerHTML = mode1.find( ‘data‘ ); } ) }) var controler2 = Controler.create( mode2 view2, function(){ view1.el.find( ‘‘div‘ ).bind( ‘‘click‘, function(){ this.innerHTML = mode2.find( ‘data‘ ); } ) })
觀察者模式
觀察者模式是這樣一種設計模式。
一個被稱作被觀察者的對象,維護一組被稱為觀察者的對象,這些對象依賴於被觀察者,被觀察者自己主動將自身的狀態的不論什麽變化通知給它們。
當一個被觀察者須要將一些變化通知給觀察者的時候,它將採用廣播的方式,這條廣播可能包括特定於這條通知的一些數據。
當特定的觀察者不再須要接受來自於它所註冊的被觀察者的通知的時候,被觀察者能夠將其從所維護的組中刪除。
通俗點理解,就是面試官是被觀察者。而等待通知的人是觀察者。
javascript中平時接觸的dom事件。事實上就是一種觀察者模式的體現:
div.onclick = function click (){ alert ( ‘‘click‘ ) }
僅僅要訂閱了div的click事件,當點擊div的時候,click函數就會運行。
觀察者模式能夠非常好的實現連個模塊之間的解耦,假如一個多人合作的項目,我負責Map和Gamer模式:
loadImage( imgAry, function(){ Map.init(); Gamer.init(); } )
<p>而別人負責loadImage方法的維護。假設有一天,我要加上一個Sound模塊,而我無權動用別人的代碼。僅僅能空空等待別人回來加入:</p><pre name="code" class="html">loadImage( imgAry, function(){ Map.init(); Gamer.init(); Sount.init(); } )
所以我們能夠做例如以下修改:
loadImage.listen( ‘‘ready‘, function(){ Map.init(); }) loadImage.listen( ‘‘ready‘, function(){ Gamer.init(); }) loadImage.listen( ‘‘ready‘, function(){ Sount.init(); })
loadImage完畢之後。不用再關心未來會發送什麽,接下來它僅僅須要發送一個信號:
loadImage.trigger( ‘ready’ );
全部監聽ready事件的對象就都會收到通知。這就是觀察者模式的應用場景。
講到這裏。經常使用的設計模式也講完了,深入理解javascript系列也將告一段落。
深入理解javascript之設計模式