js常用15種設計模式

對《js設計模式與開發實踐》一書的總結,感謝作者,以下是筆者自己碼的字,挺辛苦的,給個贊吧,轉載,請添加源鏈接

一、常用設計模式

1、單例模式:確保只有一個實例,並提供全局訪問。

2、策略模式:定義一些列的算法,把它們一個個封裝起來,並且使它們可以相互替換。

3、代理模式:爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。js裏虛擬代理(網絡請求方面)、緩存代理(數據方面)最常用

4、迭代器模式:提供一種方法,順序訪問一個聚合對象中的各個元素,而又不需要暴露該對象的內部表示。不需要關心對象的內部構造,也可以按順序訪問其中的每個元素。很多語言都有自己內置的迭代器,比如js的Array.prototype.forEach

5、發佈-訂閱模式:又叫觀察者模式,它定義對象間的一種一對多的依賴關係,當一個對象的狀態生改變時,所有依賴於它的對象都將得到通知。在js中,一般用事件模型代替它。

PS:

var a={}
a.b={}
a.c=function(key,fn){
  if(!this.b[key]){
    this.b[key]=[]
     }
  this.b[key].push(fn)
}
a.d=function(){
  var key =Array.prototype.shift.call(arguments),
      fns=this.b[key]
    console.log(arguments)
  if(!fns||fns.length===0){
    return false;
     }
  for(var i=0,fn;fn=fns[i++];){
    fn.apply(this,arguments)
  }
}
a.c('88',function(pri){
    console.log(pri)
})
a.c('110',function(pri){
    console.log(pri)
})
a.d('88',200)
a.d('110',300)

 

6、命令模式:執行某些特定事情的指令。記錄信息的清單,就是命令模式中的命令對象。

PS:有時候,需要像某些對象發送請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是什麼。這時候就需要命令模式,使得請求發送者和請求接收者能夠消除耦合關係。

7、組合模式:使用樹形方式創建對象的結構,把相同的操作應用在組合對象和單個對象上。

8、模板方法模式:只需要集成就可以實現,由兩部分組成,第一部分是抽象類,第二部分是具體的實現子類。通常在抽象父類中封裝了子類的算法框架,包括實現一些公共方法以及封裝子類中所有方法的執行順序。子類通過繼承這個抽象類,也繼承了整個算法結構,並且可以選擇重寫父類的方法。

PS:

//抽象類
var a=function(){};
a.prototype.b=function(){
    console.log('把水煮沸')
}
a.prototype.c=function(){}
a.prototype.d=function(){}
a.prototype.e=function(){}
a.prototype.init=function(){
    this.b();
    this.c();
    this.d();
    this.e();
}
//子類
var g=function(){}
g.prototype.c=function(){
    console.log('用沸水衝咖啡')
}
g.prototype.d=function(){
    console.log('把咖啡倒進杯子')
}
g.prototype.e=function(){
    console.log('加糖和牛奶')
}
var h=new g()
h.init()

這裏的a.prototype.init就是模板方法

9、好萊塢原則:即高層組件調用底層組件,模板方法是好萊塢的一個典型使用場景;子類放棄了對自己的控制權,而是改爲父類通知子類哪些方法應該在什麼時候被調用。作爲子類,只負責提供一些設計上的細節。

Ps:發佈-訂閱模式、回調函數都用到了此原則

10、享元模式:運用共享技術來有效支持大量細粒度的對象。

Ps: 

 

  1. 內部狀態存儲與對象內部。
  2. 內部狀態可以被一些對象共享。
  3. 內部狀態獨立於具體的場景,通常不會改變。
  4. 外部狀態取決於具體的場景,並根據場景而變化,外部狀態不能被共享。

11、對象池:維護一個裝在空閒對象的池子,如果需要對象的時候,不是直接new,而是轉從對象池裏獲取。如果對象池裏沒有空閒對象,則創建一個新的對象,當獲取出的對象完成它的職責後,再進入池子等待被下次獲取;

12、職責鏈模式:使多個對象都有機會處理請求,並從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。

PS:有多個if-else條件的時候,要去考慮是否可以用職責鏈模式。

13、中介者模式:解除對象與對象之間得耦合關係。增加一箇中介者對象後,所有得相關對象都通過中介者對象來通信,而不是互相引用,所有當一個對象發生改變時,只需要通知中介者對象即可。此模式迎合迪米特法則的一種實現,迪米特也叫做最少知識原則,是指一個對象應該儘可能少地瞭解另外的對象。

PS:編寫思路:1、利用發佈-訂閱模式2、在中介者對象中開放一些接收消息得接口

14、裝飾者模式:給對象動態地增加職責。這種方式並沒有真正地改動對象自身,而是將對象放入另一個對象之中,這些對象以一條鏈的方式進行引用,形成一個聚合對象。

PS:經常用到Function.pototype.after和Function.pototype.before兩個函數進行裝飾

Function.prototype.before=function(beforefn){

    var _self=this;//保存原函數引用

    return function(){// 返回包含了原函數和新函數的“代理”函數

        beforefn.apply(this,arguments);// 執行新函數,且保證this不被劫持,新函數接受的參數也會被原封不動地傳入原函數,新函數在原函數之前執行

        return _self.apply(this,arguments);// 執行原函數並返回原函數的執行結果,並且保證this不被劫持

    }

}   

注意:這裏的beforefn和原函數_self共用一組參數列表arguments,當我們在beforefn的函數體內改變arguments的時候,_self接收的參數列表自然也會變化,所以經常用於動態地改變原函數的參數

15、狀態模式:關鍵是區分事物內部的狀態,事物內部狀態的改變往往會帶來事物的行爲改變。此模式關鍵是把事物的每種狀態都封裝成單獨的類。

PS:狀態模式和策略模式區別:

策略模式的各個策略類之間是平等又平行的,他們之間沒有任何聯繫,所以客戶必須熟知這些策略類的作用。以便隨時切換算法;而狀態模式,狀態和狀態對應的行爲早已被封裝好的,狀態之間的切換也早被規定完成,“改變行爲”這件事發生在狀態模式內部。對客戶來說,並不需要了解這些細節,這正是狀態模式的作用所在。

16、適配器模式:解決兩個軟件實體的接口不兼容的問題。不考慮接口是怎樣實現的,也不考慮將來可能會如何讓變化,適配器模式不需要改變已有的接口,就能夠使他們協同作用,適配器模式通常只包裝一次。

PS:

var a={
    show:function(){
        console.log('a數據源開始渲染')
    }
}
var b={
    disply:function(){
        console.log('b數據源開始渲染')
    }
}
//適配器c
var c={
    show:function(){
        return b.disply();
    }
}
var render=function(fn){
    fn.show()
}
render(a)
render(c)

17、外觀模式:在JS中不常用。主要是爲子系統中的一組接口提供一個一致的界面,定義了一個高層的接口,這個接口使子系統更加容易使用。在C++或者JAVA中指的是一組類的集合,這些類相互協作可以組成系統中一個相對獨立的部分。但在JS中,這個子系統至少應該指的是一組函數的集合。

PS:

var A=function(){
    a1()
    a2()
}

 

二、番外篇:

1、單一職責原則(SRP):一個對象或方法只做一件事。代理、單例、迭代、裝飾模式經常用到。

PS:

  1. 如果隨着需求的變化,有兩個職責總是同時變化,那就不必分離他們。比如ajax,創建xhr和發送xhr總是在一起,那麼這個過程就沒必要分開
  2. 職責的變化軸線僅當它們確定會發生變化時才具有意義,即使兩個職責已經被耦合在一起,但他們還沒有發生改變的徵兆,那麼也許沒有必要主動分離它們,重構的時候在進行分離也不遲。

2、最少知識原則(LKP):也叫“迪米特法則”,一個軟件實體應當儘可能少地與其他實體發生相互作用。軟件實體指的是系統、類、模塊、函數、變量、對象等等。

PS:

  1. 儘量減少對象之間的交互。
  2. 非要聯繫的化,可使用第三者對象承擔兩者之間的通信。
  3. 也可以讓對象暴露必要的接口,讓對象之間的聯繫限制在最小範圍內。
  4. 閉包,很適合此原則。

此原則,也要根據實際開發中,情況而定,不能一味的遵守最少原則,不然會不斷的增加第三對象,龐大到難以維護。

3、開放-封閉原則:軟件實體(類、模塊、函數)等應該是可以擴展的,但是不可修改。

PS:

  1. 最明顯的就是找出程序中將要發生變化地地方,然後把變化封裝起來。
  2. 通過封裝變化的方式,可以把系統中穩定不變的部分和容易變化的部分隔離開來。在系統中,只需替換容易變化的部分,如果這些部分被封裝好了,替換也就容易多了(可以指上面例子的b\c\d)。而變化的部分之外(不變的,可以指上面例子的a)就是穩定的部分,穩定的部分是不需要改變的。
  3. Hook、回調函數都可以幫助解決開閉原則
  4. 回調函數,把部分容易變化的邏輯封裝在回調函數裏,然後把回調函數當作參數傳入一個穩定和封閉的函數中。這是高階函數的技法
  5. 挑選出最容易發生變化的地方,然後構造抽象來封閉這些變化。
  6. 在不可避免發生修改的時候,儘量修改哪些相對容易的地方。比如,修改某個開源庫的配置文件,總比修改它的源碼簡單的多。
  7. 把if換成switch-case是沒用的,換湯不換藥。看到if和switch的時候,要考慮是否可以用對象的多態性來重構它們。把不變的部分隔離出來,把可變的部分封裝起來。
  8. var a=function(m){
        m.sound()
    }
    var b=function(){}
    b.prototype.sound=function(){
        console.log('bbbb')
    }
    var c=function(){}
    c.prototype.sound=function(){
        console.log('cccc')
    }
    a(new b())
    a(new c())
    
    //增加一個跟b和c一樣的行爲對象d,不用該a函數
    var d=function(){}
    d.prototype.sound=function(){
        console.log('dddd')
    }
    a(new d())

三、寫代碼注意點:

  1. 在最初編寫代碼的時候,先假設變化永遠不會發生,這有利於我們速度完成需求。當變化發生並且對我們接下來的工作造成影響的時候,可以在回過頭來封裝這些變化的地方。然後確保我們不會掉進同一個坑裏。
  2. 開閉原則是編寫一個好程序的目標,其他設計原則和設計模式都是達到這個目標的過程。
  3. 將不變的部分和變化的部分隔開是每個設計模式的主題
  4. 虛擬代理把一些開銷很大的對象,延遲到真正需要它的時候纔去創建。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章