《JavaScript設計模式與開發實踐》做者:曾探javascript
編譯時就能發現類型不匹配的錯誤,編輯器能夠幫助咱們提早避免程序在運行中可能發生的一些錯誤;編譯器能夠針對數據類型對程序進行一些優化工做;css
迫使程序員按照契約來編寫;類型的聲明會增長更多的代碼;html
編寫的代碼數量更少,看起來也更簡潔,程序員能夠把更多精力放在業務邏輯;給編碼帶來了很大的靈活性,無需進行類型檢測,能夠嘗試調用任何對象的任意方法,無需考慮它本來是否被設計爲擁有該方法,創建在鴨子類型上。java
沒法保證變量的類型,從而在程序運行期可能發生跟類型相關的錯誤;node
「若是它走起路來像鴨子,叫起來也像鴨子,那麼它就是鴨子。」程序員
鴨子類型指導咱們只關注對象的行爲,而不關注對象自己,即灌輸HAS-A,而不是IS-A。利用鴨子類型的思想,沒必要藉助超類型的幫助,就能夠輕鬆實現:「面向接口編程,而不是面向實現編程。」例如:一個對象如有push和pop方法,而且提供了正確的實現,他就能夠被當成棧來使用。算法
同一操做做用於不一樣的對象上面,能夠產生不一樣的解釋和不一樣的執行結果。換句話說,給不一樣對象發送同一個消息的時候,這些對象會根據這個消息分別給出不一樣的反饋。編程
實際上時把「作什麼」和「誰去作」分離開來,消除類型之間的耦合關係,js對象的多態性時與生俱來的。設計模式
把過程化的條件分支語句轉化爲對象的多態性,從而消除這些分支語句。數組
經過向上轉型:當給一個類變量賦值時,這個變量的類既可使用這個類自己,也可使用這個類的超類。使用繼承來獲得多態效果,是讓對象表現出多態性的最經常使用手段:包括實現繼承、接口繼承。
js的變量類型在運行期是可變的,一個對象能夠表示不一樣類型的對象,js對象的多態性是與生俱來的。
封裝數據、封裝實現、封裝類型、封裝變化。
一般是由語法解析實現(private、public、protected),js只能經過變量的做用域實現,而且只能模擬出public和private這兩種封裝性。
對象內部的變化對其餘對象是透明不可見的;對象對它本身的行爲負責;其餘對象不關心它的內部實現;封裝使得對象之間的耦合變鬆散,對象之間只經過暴露的API接口來通訊。
靜態語言中一種重要的封裝方式,通常經過抽象類和接口來進行,把對象真正的類型隱藏在抽象類或者接口以後,相比對象的類型,客戶更關心對象的行爲。封裝類型方面,js沒有能力,也沒有必要作得更多。
經過封裝變化的方式,把系統中穩定不變的部分和容易改變的部分隔離開來,在系統的演變過程當中,咱們只須要替換那些容易變化的部分,若是這些部分是已經封裝好的,替換起來也相對容易,這能夠最大程度的保證程序的穩定性和可拓展性。
以類爲中心的面向對象編程語言中,類和對象的關係能夠想象成鑄模和鑄件的關係,對象老是從類中建立而來。原型編程的思想中,類並非必需的,對象是經過克隆另一個對象獲得的。
既是一種設計模式也被稱爲一種編程範型。原型模式是用於建立對象的一種模式,不關心對象的具體類型,找到一個對象,經過克隆來建立一個一摸同樣的對象。
語言自己是否提供了clone方法,es5提供了Object.create方法,能夠用來克隆對象。
提供了一種便捷的方式去建立某個類型的對象。
基於原型鏈的委託機制。
當對象沒法響應某個請求時,會把該請求委託給它的原型。
設計者本意,除了undefined以外,一切都應該是對象,因此存在「包裝類」。js不能說全部的數據都是對象,但能夠說絕大部分數據都是對象,js中存在Object.prototype對象,其餘對象追根溯源都克隆於這個根對象,Object.prototype是它們的原型。
js語言中,咱們不須要關係克隆的細節,引擎內部負責實現,只要顯示的調用var obj1 = new Object()或者var obj2 = {}。引擎內部會從Object.prototype上克隆一個對象出來。用new運算符來建立對象的多城,實際上也只是先克隆Object.prototype對象,再進行一些其餘額外操做。
js給對象提供了一個名爲proto的隱藏屬性,默認會指向它的構造器的原型對象,它就是對象跟它的原型聯繫起來的紐帶。
原型鏈查找
設計模式在不少時候都體現了語言的不足之處
老是指向一個對象,而具體指向哪一個對象是在運行時基於執行環境動態綁定的,而非函數被聲明時的環境。
傳入參數形式不一樣,它們第一個參數都是指定函數體內this對象的指向,apply第二個參數爲一個帶下表的集合,能夠是數組或者類數組,call第二個參數開始,每一個參數依次被傳入函數。apply比call的使用率更高,call是包裝在apply上面的語法糖,若是咱們明確的知道函數接受多少個參數,而且想一目瞭然地表達形參和實參的對應關係,適合使用call來傳送。
Function.prototype.bind = function ( context ) {
var self = this;
return function () {
return self.apply( context, arguments );
}
};複製代碼
js是一門完整的面向對象的編程語言,同時也擁有許多函數式語言的特性。
變量的有效範圍,在函數聲明變量時沒有帶關鍵字var就會變成全局變量,使用了var時稱爲局部變量,只有在該函數內部才能訪問到這個變量,在函數外面時訪問不到的。js中函數能夠用來創造函數做用域。在函數裏面能夠看到外面的變量,而在函數外面沒法看到函數裏面的變量,這是由於在函數中搜索一個變量的時候,若是該函數內並無聲明這個變量,那麼搜索的過程會隨着代碼執行環境建立的做用域鏈往外層逐層搜索,一直搜索到全局對象爲止。
全局變量的生存週期是永久的,除非主動銷燬。在函數內使用var聲明的局部變量,在函數退出時,這些局部變量記失去了它們的價值,會隨着函數調用的結束而被銷燬。
由於對外部做用域的引用能夠阻止外部的做用域被銷燬,延長了局部變量的生命週期、能夠把每次循環中的i值都封閉起來,使循環綁定事件符合咱們的預期
提煉函數時代碼重構中的一種常見技巧。若是在一個大函數中有一些代碼塊可以獨立出來,經常把這些代碼塊封裝在獨立的小函數裏面。獨立的小函數有助於代碼複用,若是有良好的命名,自己也起到了註釋的效果,若是這些小函數不須要在程序的其餘地方使用,最好是把它們用閉包封閉起來。
對象以方法的形式包括了過程,閉包在過程當中以環境的形式包含了數據。一般用面向對象思想能實現的功能,用閉包也能實現,反之亦然。
命令模式的意圖是把請求封裝爲對象,從而分離請求的發起者和請求的接收者(執行者)之間的耦合關係。
解決對象間循環引用帶來的內存泄漏問題,只須要把循環引用中的變量設爲null便可。將變量設置爲null意味着切斷變量與它此前引用的值之間的鏈接。當垃圾收集器瑕疵運行時,就會刪除這些值並回收它們佔用的內存
知足下列條件之一的函數:
這表明着咱們能夠抽離一部分容易變化的業務邏輯,把這部分業務邏輯放在函數參數中,這樣依賴能夠分離業務代碼中變化與不變的部分。例如:
var isType = function(type){
return function( obj ) {
return Object.prototype.toString.call( obj ) === ‘[object ‘ + type + ‘]’;
}
};
var isString = isType(‘String’);
var isArray = isType(‘Array’);
var isNumber = isType(‘Number’);複製代碼
2.getSinglevar getSingle = function ( fn ) {
var ret;
return function ( ) {
return ret || (ret = fn.apply ( this, arguments ) );
};
};複製代碼
AOP(面向切面編程)的主要做用是吧一些跟核心業務邏輯模塊無關的功能抽離出來,這些跟業務邏輯無關的功能一般包括日誌統計、安全控制、異常處理等。把這些功能抽離出來以後,再經過「動態織入」的方式摻入業務邏輯模塊中。優勢首先是能夠保持業務邏輯模塊的純淨和高內聚性,其次是能夠很方便的複用日誌統計等功能模塊。js中實現AOP更簡單,這是js與生俱來的能力,這種使用AOP的方式給函數添加職責,也是js語言中一種很是特別和巧妙的裝飾者模式實現。
函數節流:
函數被頻繁調用的場景:window.onresize事件、mousemove事件、上傳進度
函數節流的原理:藉助setTimeout來完成
函數節流的代碼實現:
var throttle = function ( fn, interval ) {
var _self = fn,
timer,
firstTime = true;
return function () {
var args = arguments,
_me = this;
if ( fisrtTime ) {
_self.apply( _me, args );
return firstTime = false;
}
if ( timer ) {
return false;
}
timer = setTimeout ( function ( ) [
clearTimeout ( timer );
timer = null;
_self.apply ( _me, args );
}, interval || 500 );
};
};
window.onresize = throttle ( function ( ) {
console.log ( 1 );
}, 500 );複製代碼
實現一些只須要一個的對象,好比線程池、全局緩存、window對象等
用一個變量來標誌當前是否已經爲某個類建立過對象,若是是,則在下一次獲取該類的實例時,直接返回以前建立的對象。
用戶從這個類中建立對象的時候,能夠像使用其餘任何普通的類同樣。
var CreateDiv = function (html) {
this.html = html;
this.init();
};
CreateDiv.prototype.init = function () {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
};
var ProxySingleCreateDiv = (function () {
var instance;
return function (html) {
if (!instance) {
instance = new CreateDiv(html);
}
return instance;
}
})();
var a = new ProxySingleCreateDiv('seven1');
var b = new ProxySingleCreateDiv('seven2');
alert(a === b);複製代碼
能夠將全局變量看成單例模式來使用,可是全局變量會污染命名空間。可使用如下幾種方法避免全局空間的污染:
惰性單例是指在須要的時候才建立對象實例。
var getSingle = function( fn ) {
var result ;
return function ( ) {
return result || ( result = fn.apply ( this, arguments ) );
}
};複製代碼
實現一個功能有多個方案能夠選擇
定義一系列的算法,把它們一個個封裝起來,而且使它們能夠互相替換。
第一個部分是一組策略類,策略類封裝了具體的算法,並負責具體的計算過程。第二個部分是環境類Context,Context接受客戶的請求,隨後把請求委託給某一個策略類。
在js語言中,函數也是對象,因此更簡單和直接的作法是把策略和Context定義成一個函數。
全部跟算法有關的邏輯再也不放在Context中,而是分佈在各個策略隊形中。當咱們對這些策略對象發出請求時,它們會返回各自不一樣的結果,這正是對象多態性的體現,也是「它們能夠互相替換」的目的。
原理:js實現動畫效果的原理跟動畫片的製做同樣,js經過連續改變元素的某個CSS屬性,來實現動畫效果。
思路和準備工做:
運動以前,須要記錄一些有用的信息,至少包括:
經過定時器,把動畫已消耗的時間、小球原始位置、小球目標位置和動畫持續的總時間傳入緩動算法。該算法會經過這幾個參數,計算出小球當前應該所在的位置。最後再更新該div的CSS屬性,小球就能順利的動起來了。
var Animate = function( dom ){
this.dom = dom; // 進行運動的dom 節點
this.startTime = 0; // 動畫開始時間
this.startPos = 0; // 動畫開始時,dom 節點的位置,即dom 的初始位置
this.endPos = 0; // 動畫結束時,dom 節點的位置,即dom 的目標位置
this.propertyName = null; // dom 節點須要被改變的css 屬性名
this.easing = null; // 緩動算法
this.duration = null; // 動畫持續時間
};
Animate.prototype.start = function( propertyName, endPos, duration, easing ){
this.startTime = +new Date; // 動畫啓動時間
this.startPos = this.dom.getBoundingClientRect()[ propertyName ]; // dom 節點初始位置
this.propertyName = propertyName; // dom 節點須要被改變的CSS 屬性名
this.endPos = endPos; // dom 節點目標位置
this.duration = duration; // 動畫持續事件
this.easing = tween[ easing ]; // 緩動算法
var self = this;
var timeId = setInterval(function(){ // 啓動定時器,開始執行動畫
if ( self.step() === false ){ // 若是動畫已結束,則清除定時器
clearInterval( timeId );
}
}, 19 );
};
Animate.prototype.step = function(){
var t = +new Date; // 取得當前時間
if ( t >= this.startTime + this.duration ){ // (1)
this.update( this.endPos ); // 更新小球的CSS 屬性值
return false;
}
var pos = this.easing( t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration );
// pos 爲小球當前位置
this.update( pos ); // 更新小球的CSS 屬性值
};
Animate.prototype.update = function( pos ){
this.dom.style[ this.propertyName ] = pos + 'px';
};
var div = document.getElementById( 'div' );
var animate = new Animate( div );
animate.start( 'left', 500, 1000, 'strongEaseOut' );複製代碼
把算法的含義擴散開來,使策略模式也能夠用來封裝一系列的「業務規則」,只要這些業務規則指向的目標一致,而且能夠被替換使用,咱們就能夠用策略模式來封裝它們。
首先,使用策略模式會在程序中增長許多策略類或者策略對象,但實際上這比把它們負責的邏輯堆在Context中要好
其次,要使用策略模式,必須瞭解全部的strategy,必須瞭解各個strategy之間的不一樣點,這樣才能選擇一個合適的strategy。
在js中除了使用類來封裝算法和行爲以外,使用函數固然也是一種選擇。這些「算法」能夠被封裝在函數中而且四處傳遞,也就是咱們常說的「高階函數」
代理模式是爲一個對象提供一個待用品或佔位符,以便控制對它的訪問。
明星的經紀人代替明星協商。
控制不一樣權限的對象對目標對象的訪問,叫做保護代理;把一些開銷很大的對象,延遲到真正須要它的時候再去建立,叫做虛擬代理。js中不容易實現保護代理,虛擬代理是最經常使用的一種代理模式。
一個類(也包括對象和函數)應該僅有一個引發它變化的緣由。若是一個對象承擔了多項職責,就意味着這個對象將變得巨大,面向對象設計鼓勵將行爲分佈到顆粒度的對象之中,若是一個對象承擔的職責過多,等於把這些職責耦合到了一塊兒,這種耦合會致使脆弱和低內聚的設計。當變化發生時,設計可能會遭到意外的破壞。
優勢:
var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return function( src ){
imgNode.src = src;
}
})();
var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage( this.src );
}
return function( src ){
myImage( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );
img.src = src;
}
})();
proxyImage( 'http:// imgcache.qq.com/music// N/k/000GGDys0yA0Nk.jpg' );複製代碼
緩存代理能夠爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,若是傳遞進來的參數跟以前一致,則能夠直接返回前面的運算結果。
提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。迭代器模式能夠把迭代的過程從業務邏輯中分離出來,在使用迭代器模式以後,即便不關心對象的內部構造,也能夠按順序訪問其中的每一個元素。
不管內部迭代器仍是外部迭代器,只要迭代的聚合對象擁有length屬性並且能夠用下標訪問,那它就能夠被迭代。
var each = function( ary, callback ){
for ( var i = 0, l = ary.length; i < l; i++ ){
if ( callback( i, ary[ i ] ) === false ){ // callback 的執行結果返回false,提早終止迭代
break;
}
}
};
each( [ 1, 2, 3, 4, 5 ], function( i, n ){
if ( n > 3 ){ // n 大於3 的時候終止循環
return false;
}
console.log( n ); // 分別輸出:1, 2, 3
});複製代碼
不管MVC仍是MVVM都少不了發佈-訂閱模式,js自己也是一門基於事件驅動的語言。
時間上解耦、對象之間解耦。
建立訂閱者自己要消耗必定的時間和內存、過分使用致使對象和對象之間的必要聯繫也將被深埋致使程序難以維護和理解
取代對象之間硬編碼的通知機制,一個對象不用顯式地調用另外一個對象的某個接口。讓兩個對象鬆耦合地聯繫在一塊兒,雖然不清楚彼此間的細節,但這不影響它們之間相互通訊。
註冊回調函數代替傳統的發佈-訂閱模式,更加優雅、簡單
//因此咱們把發佈—訂閱的功能提取出來,放在一個單獨的對象內:
var event = {
clientList: [],
listen: function( key, fn ){
if ( !this.clientList[ key ] ){
this.clientList[ key ] = [];
}
this.clientList[ key ].push( fn ); // 訂閱的消息添加進緩存列表
},
trigger: function(){
var key = Array.prototype.shift.call( arguments ), // (1);
fns = this.clientList[ key ];
if ( !fns || fns.length === 0 ){ // 若是沒有綁定對應的消息
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments ); // (2) // arguments 是trigger 時帶上的參數
}
}
};
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};複製代碼
最簡單和優雅的模式之一,命令模式中的命令(command)指的是一個執行某些特定事情的指令。
有時須要向某些對象發送請求,可是不知道請求的接受者是誰,也不知道請求的操做是什麼。
js做爲將函數做爲一等對象的語言,跟策略模式同樣,命令模式也早已融入到了js語言中,能夠用高階函數很是方便的實現命令模式,是一種隱式的模式。
var setCommand = function( button, func ){
button.onclick = function(){
func();
}
};
var MenuBar = {
refresh: function(){
console.log( '刷新菜單界面' );
}
};
var RefreshMenuBarCommand = function( receiver ){
return function(){
receiver.refresh();
}
};
var refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar );
setCommand( button1, refreshMenuBarCommand );複製代碼
通常來講命令模式都會在command對象中保存一個接收者來負責真正執行客戶的請求,這種狀況下命令對象是「傻瓜式」的,它只負責把客戶的請求轉交給接受者來執行,這種模式的好處是請求發起者和接受者之間儘量地獲得瞭解耦。「聰明」的命令對象能夠直接實現請求,這樣以來就再也不須要接受者的存在,這種「聰明」的命令對象也叫做智能命令。
用小的子對象構建更大的對象,這些小的子對象自己也許是由更小的對象構成的。
<html>
<body> <button id="button">按我</button> </body>
<script>
var MacroCommand = function(){
return {
commandsList: [],
add: function( command ){
this.commandsList.push( command );
},
execute: function(){
for ( var i = 0, command; command = this.commandsList[ i++ ]; ){
command.execute();
}
}
}
};
var openAcCommand = {
execute: function(){
console.log( '打開空調' );
}
};
/**********家裏的電視和音響是鏈接在一塊兒的,因此能夠用一個宏命令來組合打開電視和打開音響的命令 *********/
var openTvCommand = {
execute: function(){
console.log( '打開電視' );
}
};
var openSoundCommand = {
execute: function(){
console.log( '打開音響' );
}
};
var macroCommand1 = MacroCommand();
macroCommand1.add( openTvCommand );
macroCommand1.add( openSoundCommand );
/*********關門、打開電腦和打登陸QQ 的命令****************/
var closeDoorCommand = {
execute: function(){
console.log( '關門' );
}
};
var openPcCommand = {
execute: function(){
console.log( '開電腦' );
}
};
var openQQCommand = {
execute: function(){
console.log( '登陸QQ' );
}
};
var macroCommand2 = MacroCommand();
macroCommand2.add( closeDoorCommand );
macroCommand2.add( openPcCommand );
macroCommand2.add( openQQCommand );
/*********如今把全部的命令組合成一個「超級命令」**********/
var macroCommand = MacroCommand();
macroCommand.add( openAcCommand );
macroCommand.add( macroCommand1 );
macroCommand.add( macroCommand2 );
/*********最後給遙控器綁定「超級命令」**********/
var setCommand = (function( command ){
document.getElementById( 'button' ).onclick = function(){
command.execute();
}
})( macroCommand );
</script>
</html>複製代碼
一種只須要使用繼承就能夠實現的很是簡單的模式
var Coffee = function(){};
Coffee.prototype = new Beverage();
Coffee.prototype.brew = function(){
console.log( '用沸水沖泡咖啡' );
};
Coffee.prototype.pourInCup = function(){
console.log( '把咖啡倒進杯子' );
};
Coffee.prototype.addCondiments = function(){
console.log( '加糖和牛奶' );
};
var Coffee = new Coffee();
Coffee.init();
Beverage.prototype.init = function(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
};
var Tea = function(){};
Tea.prototype = new Beverage();
Tea.prototype.brew = function(){
console.log( '用沸水浸泡茶葉' );
};
Tea.prototype.pourInCup = function(){
console.log( '把茶倒進杯子' );
};
Tea.prototype.addCondiments = function(){
console.log( '加檸檬' );
};
var tea = new Tea();
tea.init();複製代碼
由兩部分結構組成:第一部分是抽象父類,第二部分是具體的實現子類。一般在抽象父類中封裝了子類的算法框架,包括實現一些公共方法以及封裝子類中全部方法的執行順序。子類經過繼承這個抽象類,也繼承了整個算法結構,而且能夠選擇重寫父類的方法。
不該被實例化,用來被某些具體類繼承的。用於向上轉型、爲子類定義公共接口。
沒有具體的實現過程,當子類繼承這個抽象類時,必須重寫抽象方法
具體實現方法
抽象類的一個做用時隱藏對象的具體類型,由於js時一門「類型模糊」的語言,因此隱藏對象在js中並不總要。使用原型繼承來墨跡傳統的類繼承時,並無編譯器幫助咱們進行任何形式的檢查,咱們也沒有辦法保證子類會重寫父類中的「抽象方法」
容許底層組件將本身掛鉤到高層組件中,高層組件會決定何時、以何種方式去使用這些底層組件。模版方法模式是好萊塢原則的一個典型使用場景,它與好萊塢原則的聯繫很是明顯,當咱們用模版方法編寫一個程序時,就意味着子類放棄了對本身的控制權,而是改成父類通知子類,哪些方法應該在何時被調用。做爲子類,只負責提供一些設計上的細節。還適用於其餘模式和場景,例如發佈—訂閱模式和回調函數。
一種用於性能優化的模式,fly在這裏是蒼蠅的意思,意爲蠅量級,核心是運用共享技術來有效支持大量細粒度的對象。
把內部狀態和外部狀態分離開來。有多少內部狀態的組合,系統便最多存在多少個共享對象,而外部狀態則儲存在共享對象的外部,在必要時傳入共享對象來組裝成一個完整的對象。
也能夠用對象池+事件委託來代替實現
var Upload = function( uploadType){
this.uploadType = uploadType;
};
Upload.prototype.delFile = function( id ){
uploadManager.setExternalState( id, this ); // (1)
if ( this.fileSize < 3000 ){
return this.dom.parentNode.removeChild( this.dom );
}
if ( window.confirm( '肯定要刪除該文件嗎? ' + this.fileName ) ){
return this.dom.parentNode.removeChild( this.dom );
}
}
var UploadFactory = (function(){
var createdFlyWeightObjs = {};
return {
create: function( uploadType){
if ( createdFlyWeightObjs [ uploadType] ){
return createdFlyWeightObjs [ uploadType];
}
return createdFlyWeightObjs [ uploadType] = new Upload( uploadType);
}
}
})();
var uploadManager = (function(){
var uploadDatabase = {};
return {
add: function( id, uploadType, fileName, fileSize ){
var flyWeightObj = UploadFactory.create( uploadType );
var dom = document.createElement( 'div' );
dom.innerHTML =
'<span>文件名稱:'+ fileName +', 文件大小: '+ fileSize +'</span>' +
'<button class="delFile">刪除</button>';
dom.querySelector( '.delFile' ).onclick = function(){
flyWeightObj.delFile( id );
}
document.body.appendChild( dom );
uploadDatabase[ id ] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
};
return flyWeightObj ;
},
setExternalState: function( id, flyWeightObj ){
var uploadData = uploadDatabase[ id ];
for ( var i in uploadData ){
flyWeightObj[ i ] = uploadData[ i ];
}
}
}
})();
var id = 0;
window.startUpload = function( uploadType, files ){
for ( var i = 0, file; file = files[ i++ ]; ){
var uploadObj = uploadManager.add( ++id, uploadType, file.fileName, file.fileSize );
}
};複製代碼
使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。
公交車人太多了,找不到售票員,經過一個我的將錢遞給售票員
做用域鏈、原型鏈、dom節點事件冒泡
請求發送者只須要知道鏈中的第一個節點,從而弱化了發送者和接受者之間的強聯繫。
使程序中多了一些節點對象,可能在某一次請求傳遞的過程當中,大部分節點沒有起到實質性的做用,它們的做用僅僅是讓請求傳遞下去,從性能方面考慮,咱們要避免過長的職責鏈帶來的性能損耗
js開發中,職責鏈模式是最容易被忽視的模式之一。職責鏈模式能夠很好地幫助咱們管理代碼,下降發起請求的對象和處理請求的對象之間耦合性。職責鏈匯中的節點數量和順序是能夠自由變化的。
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金預購,獲得100 優惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求日後面傳遞
}
};
var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 元定金預購,獲得50 優惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求日後面傳遞
}
};
var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( '普通購買,無優惠券' );
}else{
console.log( '手機庫存不足' );
}
};
// Chain.prototype.setNextSuccessor 指定在鏈中的下一個節點
// Chain.prototype.passRequest 傳遞請求給某個節點
var Chain = function( fn ){
this.fn = fn;
this.successor = null;
};
Chain.prototype.setNextSuccessor = function( successor ){
return this.successor = successor;
};
Chain.prototype.passRequest = function(){
var ret = this.fn.apply( this, arguments );
if ( ret === 'nextSuccessor' ){
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
}
return ret;
};複製代碼
解除對象與對象之間的緊耦合關係。增長一箇中介者對象後,全部的相關對象均可以經過中介者對象來通訊,而不是互相引用,因此當一個對象發生改變時,只須要通知中介者對象便可。
中介者模式是迎合迪米特法則的一種實現。迪米特法則也叫最少知識原則,是指一個對象應該儘量少地瞭解另外的對象(相似不和陌生人說話)。若是對象之間的耦合性過高,一個對象發生改變以後,不免會影響到其餘的對象,在中介者模式中,對象之間幾乎不知道彼此的存在,它們只經過中介者對象來互相影響對方。
會新增一箇中介者對象,由於對象之間交互的複雜性,轉移成了中介者對象的複雜性,使得中介者對象常常是巨大的的。中介者對象自身每每是難以維護的對象。
通常來講,若是對象之間的複雜耦合確實致使調用和維護出現了困難,並且這些耦合度隨着項目的變化呈現指數增加曲線,那咱們就能夠考慮使用中介者模式來重構代碼。
裝飾者模式能夠動態地給某個對象添加一些額外的職責,而不會影響這個類中派生的其餘對象。
var plane = {
fire: function(){
console.log( '發射普通子彈' );
}
}
var missileDecorator = function(){
console.log( '發射導彈' );
}
var atomDecorator = function(){
console.log( '發射原子彈' );
}
var fire1 = plane.fire;
plane.fire = function(){
fire1();
missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function(){
fire2();
atomDecorator();
}
plane.fire();
// 分別輸出: 發射普通子彈、發射導彈、發射原子彈複製代碼
主要區別在於它們的意圖和設計目的。
狀態模式的關鍵是區分事物內部的狀態,事物內部的狀態的改變每每會帶來事物的行爲的改變。
把事物的每種狀態都封裝成單獨的類,跟此種狀態相關的行爲都封裝在類中
會在系統中定義許多狀態類,編寫20個狀態類是一件枯燥乏味的工做,並且系統中會所以而增長很多對象。另外,因爲邏輯分散在狀態類中,雖然避開了不受歡迎的條件分支語句,但也形成了邏輯分散的問題,咱們沒法在一個地方久看出整個狀態機的邏輯
var Light = function(){
this.offLightState = new OffLightState( this ); // 持有狀態對象的引用
this.weakLightState = new WeakLightState( this );
this.strongLightState = new StrongLightState( this );
this.superStrongLightState = new SuperStrongLightState( this );
this.button = null;
};
Light.prototype.init = function(){
var button = document.createElement( 'button' ),
self = this;
this.button = document.body.appendChild( button );
this.button.innerHTML = '開關';
this.currState = this.offLightState; // 設置默認初始狀態
this.button.onclick = function(){ // 定義用戶的請求動做
self.currState.buttonWasPressed();
}
};
var OffLightState = function( light ){
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){
console.log( '弱光' );
this.light.setState( this.light.weakLightState );
};複製代碼
適配器模式主要用來解決兩個已有接口之間不匹配的問題,它不考慮這些接口是怎樣實現的,也不考慮它們未來可能如何演化。適配器模式不須要改變已有的接口,就能把它們協同做用。
充電適配器
裝飾者模式和代理模式也不會改變原有對象的接口,但裝飾者模式的做用是爲了給對象增長功能。裝飾者模式經常會刑場一條長的裝飾鏈,而適配器模式一般只包裝一次。代理模式是爲了控制對對象的訪問,一般也只包裝一次。
外觀模式的做用卻是和適配器比較類似,有人把外觀模式當作一組對象的適配器,但外觀模式最顯著的特色是定義了一個新的接口。
var googleMap = {
show: function(){
console.log( '開始渲染谷歌地圖' );
}
};
var baiduMap = {
display: function(){
console.log( '開始渲染百度地圖' );
}
};
var baiduMapAdapter = {
show: function(){
return baiduMap.display();
}
};
renderMap( googleMap ); // 輸出:開始渲染谷歌地圖
renderMap( baiduMapAdapter ); // 輸出:開始渲染百度地圖複製代碼
單一職責原則的職責被定義爲「引發變化的緣由」。若是咱們有兩個動機去改寫一個方法,那麼這個方法就具備兩個職責。SRP原則體現爲:一個對象(方法)只作一件事情。
代理模式、迭代器模式、單例模式和裝飾者模式
若是隨着需求的變化,有兩個職責老是同時變化,那就沒必要分離他們;職責的變化軸線僅當它們肯定會發生變化時才具備意義,即便兩個職責已經被耦合在一塊兒,但它們尚未發生改變的徵兆,那麼也許沒有必要主動分離它們,在代碼須要重構的時候再進行分離液不遲
下降了單個類或者對象的複雜度,按照職責把對象分解成更小的粒度,有助於代碼的附庸,也有利於單元測試。當一個職責須要變動的時候,不會影響到其餘職責。
增長編寫代碼的複雜度。當咱們按照職責把對象分解成更小的粒度以後,實際上也增大了這些對象之間相互聯繫的難度。
最少知識原則也叫迪米特法則
一個軟件應用應當儘量少地與其餘實體發生相互做用。這裏的軟件實體是一個廣義的概念,不只包括對象,還包括系統給、類、模塊、函數、變量等。
中介者模式、外觀模式(爲子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使子系統更佳容易使用)
軟件實體(類、模塊、函數)等應該是能夠拓展的,可是不可修改
利用對象的多態、放置掛鉤、使用回調函數
發佈-訂閱模式、模版方法模式、策略模式、代理模式、職責鏈模式
接口是對象能響應的請求的集合
感謝您耐心看完,求贊、關注,☺