JavaScript設(shè)計模式

## **JavaScript設(shè)計模式**

## **設(shè)計模式簡介**

設(shè)計模式代表了最佳的實踐,通常被有經(jīng)驗的面向?qū)ο蟮能浖_發(fā)人員所采用。設(shè)計模式是軟件開發(fā)人員在軟件開發(fā)過程中面臨的一般問題的解決方案。這些解決方案是眾多軟件開發(fā)人員經(jīng)過相當(dāng)長的一段時間的試驗和錯誤總結(jié)出來的。

設(shè)計模式是一套被反復(fù)使用的、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計經(jīng)驗的總結(jié)。使用設(shè)計模式是為了重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設(shè)計模式于己于他人于系統(tǒng)都是多贏的,設(shè)計模式使代碼編制真正工程化,設(shè)計模式是軟件工程的基石,如同大廈的一塊塊磚石一樣。

***設(shè)計模式原則***

S – Single Responsibility Principle 單一職責(zé)原則

一個程序只做好一件事

如果功能過于復(fù)雜就拆分開,每個部分保持獨立

O – OpenClosed Principle 開放/封閉原則

對擴展開放,對修改封閉

增加需求時,擴展新代碼,而非修改已有代碼

L – Liskov Substitution Principle 里氏替換原則

子類能覆蓋父類

父類能出現(xiàn)的地方子類就能出現(xiàn)

I – Interface Segregation Principle 接口隔離原則

保持接口的單一獨立

類似單一職責(zé)原則,這里更關(guān)注接口

D – Dependency Inversion Principle 依賴倒轉(zhuǎn)原則

面向接口編程,依賴于抽象而不依賴于具體

使用方只關(guān)注接口而不關(guān)注具體類的實現(xiàn)

SO體現(xiàn)較多,舉個栗子:(比如Promise)

單一職責(zé)原則:每個then中的邏輯只做好一件事

開放封閉原則(對擴展開放,對修改封閉):如果新增需求,擴展then

再舉個栗子:(此例來源-守候-改善代碼的各方面問題)

```javascript

//checkType('165226226326','mobile')

//result:false

let checkType=function(str, type) {

? ? switch (type) {

? ? ? ? case 'email':

? ? ? ? ? ? return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str)

? ? ? ? case 'mobile':

? ? ? ? ? ? return /^1[3|4|5|7|8][0-9]{9}$/.test(str);

? ? ? ? case 'tel':

? ? ? ? ? ? return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);

? ? ? ? default:

? ? ? ? ? ? return true;

? ? }

}

```

**設(shè)計模式分類**

創(chuàng)建型

單例模式

原型模式

工廠模式

抽象工廠模式

建造者模式

結(jié)構(gòu)型

適配器模式

裝飾器模式

代理模式

外觀模式

橋接模式

組合模式

享元模式

行為型

觀察者模式

迭代器模式

策略模式

模板方法模式

職責(zé)鏈模式

命令模式

備忘錄模式

狀態(tài)模式

訪問者模式

中介者模式

解釋器模式

## 工廠模式

> 工廠模式定義一個用于創(chuàng)建對象的接口,這個接口由子類決定實例化哪一個類。該模式使一個類的實例化延遲到了子類。而子類可以重寫接口方法以便創(chuàng)建的時候指定自己的對象類型。

```javascript

class Product {

? ? constructor(name) {

? ? ? ? this.name = name

? ? }

? ? init() {

? ? ? ? console.log('init')

? ? }

? ? fun() {

? ? ? ? console.log('fun')

? ? }

}

class Factory {

? ? create(name) {

? ? ? ? return new Product(name)

? ? }

}

// use

let factory = new Factory()

let p = factory.create('p1')

p.init()

p.fun()

```

**適用場景**

如果你不想讓某個子系統(tǒng)與較大的那個對象之間形成強耦合,而是想運行時從許多子系統(tǒng)中進(jìn)行挑選的話,那么工廠模式是一個理想的選擇

將new操作簡單封裝,遇到new的時候就應(yīng)該考慮是否用工廠模式;

需要依賴具體環(huán)境創(chuàng)建不同實例,這些實例都有相同的行為,這時候我們可以使用工廠模式,簡化實現(xiàn)的過程,同時也可以減少每種對象所需的代碼量,有利于消除對象間的耦合,提供更大的靈活性

**優(yōu)點**

創(chuàng)建對象的過程可能很復(fù)雜,但我們只需要關(guān)心創(chuàng)建結(jié)果。

構(gòu)造函數(shù)和創(chuàng)建者分離, 符合“開閉原則”

一個調(diào)用者想創(chuàng)建一個對象,只要知道其名稱就可以了。

擴展性高,如果想增加一個產(chǎn)品,只要擴展一個工廠類就可以。

**缺點**

添加新產(chǎn)品時,需要編寫新的具體產(chǎn)品類,一定程度上增加了系統(tǒng)的復(fù)雜度

考慮到系統(tǒng)的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進(jìn)行定義,增加了系統(tǒng)的抽象性和理解難度

什么時候不用

當(dāng)被應(yīng)用到錯誤的問題類型上時,這一模式會給應(yīng)用程序引入大量不必要的復(fù)雜性.除非為創(chuàng)建對象提供一個接口是我們編寫的庫或者框架的一個設(shè)計上目標(biāo),否則我會建議使用明確的構(gòu)造器,以避免不必要的開銷。

由于對象的創(chuàng)建過程被高效的抽象在一個接口后面的事實,這也會給依賴于這個過程可能會有多復(fù)雜的單元測試帶來問題。

**例子**

曾經(jīng)我們熟悉的JQuery的$()就是一個工廠函數(shù),它根據(jù)傳入?yún)?shù)的不同創(chuàng)建元素或者去尋找上下文中的元素,創(chuàng)建成相應(yīng)的jQuery對象

```javascript

```javascript

class jQuery {

? ? constructor(selector) {

? ? ? ? super(selector)

? ? }

? ? add() {


? ? }

? // 此處省略若干API

}

window.$ = function(selector) {

? ? return new jQuery(selector)

}

```

# 單例模式

> 一個類只有一個實例,并提供一個訪問它的全局訪問點。

```javascript

class LoginForm {

? ? constructor() {

? ? ? ? this.state = 'hide'

? ? }

? ? show() {

? ? ? ? if (this.state === 'show') {

? ? ? ? ? ? alert('已經(jīng)顯示')

? ? ? ? ? ? return

? ? ? ? }

? ? ? ? this.state = 'show'

? ? ? ? console.log('登錄框顯示成功')

? ? }

? ? hide() {

? ? ? ? if (this.state === 'hide') {

? ? ? ? ? ? alert('已經(jīng)隱藏')

? ? ? ? ? ? return

? ? ? ? }

? ? ? ? this.state = 'hide'

? ? ? ? console.log('登錄框隱藏成功')

? ? }

}

LoginForm.getInstance = (function () {

? ? let instance

? ? return function () {

? ? ? ? if (!instance) {

? ? ? ? ? ? instance = new LoginForm()

? ? ? ? }

? ? ? ? return instance

? ? }

})()

let obj1 = LoginForm.getInstance()

obj1.show()

let obj2 = LoginForm.getInstance()

obj2.hide()

console.log(obj1 === obj2)

```

**優(yōu)點**

劃分命名空間,減少全局變量

增強模塊性,把自己的代碼組織在一個全局變量名下,放在單一位置,便于維護(hù)

且只會實例化一次。簡化了代碼的調(diào)試和維護(hù)

**缺點**

由于單例模式提供的是一種單點訪問,所以它有可能導(dǎo)致模塊間的強耦合 從而不利于單元測試。無法單獨測試一個調(diào)用了來自單例的方法的類,而只能把它與那個單例作為一個單元一起測試。

**場景例子**

定義命名空間和實現(xiàn)分支型方法

登錄框

vuex 和 redux中的store

## 適配器模式

將一個類的接口轉(zhuǎn)化為另外一個接口,以滿足用戶需求,使類之間接口不兼容問題通過適配器得以解決。

```javascript

class Plug {

? getName() {

? ? return 'iphone充電頭';

? }

}

class Target {

? constructor() {

? ? this.plug = new Plug();

? }

? getName() {

? ? return this.plug.getName() + ' 適配器Type-c充電頭';

? }

}

let target = new Target();

target.getName(); // iphone充電頭 適配器轉(zhuǎn)Type-c充電頭

```

**優(yōu)點**

可以讓任何兩個沒有關(guān)聯(lián)的類一起運行。

提高了類的復(fù)用。

適配對象,適配庫,適配數(shù)據(jù)

**缺點**

額外對象的創(chuàng)建,非直接調(diào)用,存在一定的開銷(且不像代理模式在某些功能點上可實現(xiàn)性能優(yōu)化)

如果沒必要使用適配器模式的話,可以考慮重構(gòu),如果使用的話,盡量把文檔完善

**場景**

整合第三方SDK

封裝舊接口

```javascript

```javascript

// 自己封裝的ajax, 使用方式如下

ajax({

? ? url: '/getData',

? ? type: 'Post',

? ? dataType: 'json',

? ? data: {

? ? ? ? test: 111

? ? }

}).done(function() {})

// 因為歷史原因,代碼中全都是:

// $.ajax({....})

// 做一層適配器

var $ = {

? ? ajax: function (options) {

? ? ? ? return ajax(options)

? ? }

}

```

## 裝飾者模式

> 動態(tài)地給某個對象添加一些額外的職責(zé),,是一種實現(xiàn)繼承的替代方案

> 在不改變原對象的基礎(chǔ)上,通過對其進(jìn)行包裝擴展,使原有對象可以滿足用戶的更復(fù)雜需求,而不會影響從這個類中派生的其他對象

```javascript

class Cellphone {

? ? create() {

? ? ? ? console.log('生成一個手機')

? ? }

}

class Decorator {

? ? constructor(cellphone) {

? ? ? ? this.cellphone = cellphone

? ? }

? ? create() {

? ? ? ? this.cellphone.create()

? ? ? ? this.createShell(cellphone)

? ? }

? ? createShell() {

? ? ? ? console.log('生成手機殼')

? ? }

}

let cellphone = new Cellphone()

cellphone.create()

console.log('------------')

let dec = new Decorator(cellphone)

dec.create()

```

**場景例子**

比如現(xiàn)在有4 種型號的自行車,我們?yōu)槊糠N自行車都定義了一個單 獨的類?,F(xiàn)在要給每種自行車都裝上前燈、尾 燈和鈴鐺這3 種配件。如果使用繼承的方式來給 每種自行車創(chuàng)建子類,則需要 4×3 = 12 個子類。 但是如果把前燈、尾燈、鈴鐺這些對象動態(tài)組 合到自行車上面,則只需要額外增加3 個類

ES7 Decorator 阮一峰

core-decorators

**優(yōu)點**

裝飾類和被裝飾類都只關(guān)心自身的核心業(yè)務(wù),實現(xiàn)了解耦。

方便動態(tài)的擴展功能,且提供了比繼承更多的靈活性。

**缺點**

多層裝飾比較復(fù)雜。

常常會引入許多小對象,看起來比較相似,實際功能大相徑庭,從而使得我們的應(yīng)用程序架構(gòu)變得復(fù)雜起來

代理模式

是為一個對象提供一個代用品或占位符,以便控制對它的訪問

**場景**

HTML元 素事件代理

```javascript

```javascript

<ul id="ul">

? <li>1</li>

? <li>2</li>

? <li>3</li>

</ul>

<script>

? let ul = document.querySelector('#ul');

? ul.addEventListener('click', event => {

? ? console.log(event.target);

? });

</script>

```

ES6 的 proxy 阮一峰Proxy

jQuery.proxy()方法

**優(yōu)點**

代理模式能將代理對象與被調(diào)用對象分離,降低了系統(tǒng)的耦合度。代理模式在客戶端和目標(biāo)對象之間起到一個中介作用,這樣可以起到保護(hù)目標(biāo)對象的作用

代理對象可以擴展目標(biāo)對象的功能;通過修改代理對象就可以了,符合開閉原則;

**缺點**

處理請求速度可能有差別,非直接訪問存在開銷

**不同點**

裝飾者模式實現(xiàn)上和代理模式類似

裝飾者模式: 擴展功能,原有功能不變且可直接使用

代理模式: 顯示原有功能,但是經(jīng)過限制之后的

## 外觀模式

為子系統(tǒng)的一組接口提供一個一致的界面,定義了一個高層接口,這個接口使子系統(tǒng)更加容易使用

**兼容瀏覽器事件綁定**

```javascript

```javascript

let addMyEvent = function (el, ev, fn) {

? ? if (el.addEventListener) {

? ? ? ? el.addEventListener(ev, fn, false)

? ? } else if (el.attachEvent) {

? ? ? ? el.attachEvent('on' + ev, fn)

? ? } else {

? ? ? ? el['on' + ev] = fn

? ? }

};

```

**

場景**

設(shè)計初期,應(yīng)該要有意識地將不同的兩個層分離,比如經(jīng)典的三層結(jié)構(gòu),在數(shù)據(jù)訪問層和業(yè)務(wù)邏輯層、業(yè)務(wù)邏輯層和表示層之間建立外觀Facade

在開發(fā)階段,子系統(tǒng)往往因為不斷的重構(gòu)演化而變得越來越復(fù)雜,增加外觀Facade可以提供一個簡單的接口,減少他們之間的依賴。

在維護(hù)一個遺留的大型系統(tǒng)時,可能這個系統(tǒng)已經(jīng)很難維護(hù)了,這時候使用外觀Facade也是非常合適的,為系系統(tǒng)開發(fā)一個外觀Facade類,為設(shè)計粗糙和高度復(fù)雜的遺留代碼提供比較清晰的接口,讓新系統(tǒng)和Facade對象交互,F(xiàn)acade與遺留代碼交互所有的復(fù)雜工作。

參考: 大話設(shè)計模式

**優(yōu)點**

減少系統(tǒng)相互依賴。

提高靈活性。

提高了安全性

**缺點**

不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。

觀察者模式

定義了一種一對多的關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象,這個主題對象的狀態(tài)發(fā)生變化時就會通知所有的觀察者對象,使它們能夠自動更新自己,當(dāng)一個對象的改變需要同時改變其它對象,并且它不知道具體有多少對象需要改變的時候,就應(yīng)該考慮使用觀察者模式。

**

## 發(fā)布 & 訂閱

**

一對多

// 主題 保存狀態(tài),狀態(tài)變化之后觸發(fā)所有觀察者對象

```javascript

```javascript

class Subject {

? constructor() {

? ? this.state = 0

? ? this.observers = []

? }

? getState() {

? ? return this.state

? }

? setState(state) {

? ? this.state = state

? ? this.notifyAllObservers()

? }

? notifyAllObservers() {

? ? this.observers.forEach(observer => {

? ? ? observer.update()

? ? })

? }

? attach(observer) {

? ? this.observers.push(observer)

? }

}

// 觀察者

class Observer {

? constructor(name, subject) {

? ? this.name = name

? ? this.subject = subject

? ? this.subject.attach(this)

? }

? update() {

? ? console.log(`${this.name} update, state: ${this.subject.getState()}`)

? }

}

// 測試

let s = new Subject()

let o1 = new Observer('o1', s)

let o2 = new Observer('02', s)

```

**場景**

DOM事件

```javascript

document.body.addEventListener('click', function() {

? ? console.log('hello world!');

});

document.body.click()

```

vue 響應(yīng)式

**優(yōu)點**

支持簡單的廣播通信,自動通知所有已經(jīng)訂閱過的對象

目標(biāo)對象與觀察者之間的抽象耦合關(guān)系能單獨擴展以及重用

增加了靈活性

觀察者模式所做的工作就是在解耦,讓耦合的雙方都依賴于抽象,而不是依賴于具體。從而使得各自的變化都不會影響到另一邊的變化。

**缺點**

過度使用會導(dǎo)致對象與對象之間的聯(lián)系弱化,會導(dǎo)致程序難以跟蹤維護(hù)和理解

## 狀態(tài)模式

> 允許一個對象在其內(nèi)部狀態(tài)改變的時候改變它的行為,對象看起來似乎修改了它的類

```javascript

```javascript

// 狀態(tài) (弱光、強光、關(guān)燈)

class State {

? ? constructor(state) {

? ? ? ? this.state = state

? ? }

? ? handle(context) {

? ? ? ? console.log(`this is ${this.state} light`)

? ? ? ? context.setState(this)

? ? }

}

class Context {

? ? constructor() {

? ? ? ? this.state = null

? ? }

? ? getState() {

? ? ? ? return this.state

? ? }

? ? setState(state) {

? ? ? ? this.state = state

? ? }

}

// test

let context = new Context()

let weak = new State('weak')

let strong = new State('strong')

let off = new State('off')

// 弱光

weak.handle(context)

console.log(context.getState())

// 強光

strong.handle(context)

console.log(context.getState())

// 關(guān)閉

strong.handle(context)

console.log(context.getState())

```

**場景**

一個對象的行為取決于它的狀態(tài),并且它必須在運行時刻根據(jù)狀態(tài)改變它的行為

一個操作中含有大量的分支語句,而且這些分支語句依賴于該對象的狀態(tài)

**優(yōu)點**

定義了狀態(tài)與行為之間的關(guān)系,封裝在一個類里,更直觀清晰,增改方便

狀態(tài)與狀態(tài)間,行為與行為間彼此獨立互不干擾

用對象代替字符串來記錄當(dāng)前狀態(tài),使得狀態(tài)的切換更加一目了然

**缺點**

會在系統(tǒng)中定義許多狀態(tài)類

邏輯分散

## 迭代器模式

> 提供一種方法順序一個聚合對象中各個元素,而又不暴露該對象的內(nèi)部表示。

```javascript

class Iterator {

? ? constructor(conatiner) {

? ? ? ? this.list = conatiner.list

? ? ? ? this.index = 0

? ? }

? ? next() {

? ? ? ? if (this.hasNext()) {

? ? ? ? ? ? return this.list[this.index++]

? ? ? ? }

? ? ? ? return null

? ? }

? ? hasNext() {

? ? ? ? if (this.index >= this.list.length) {

? ? ? ? ? ? return false

? ? ? ? }

? ? ? ? return true

? ? }

}

class Container {

? ? constructor(list) {

? ? ? ? this.list = list

? ? }

? ? getIterator() {

? ? ? ? return new Iterator(this)

? ? }

}

// 測試代碼

let container = new Container([1, 2, 3, 4, 5])

let iterator = container.getIterator()

while(iterator.hasNext()) {

? console.log(iterator.next())

}

```

**場景例子**

Array.prototype.forEach

jQuery中的$.each()

ES6 Iterator

**特點**

訪問一個聚合對象的內(nèi)容而無需暴露它的內(nèi)部表示。

為遍歷不同的集合結(jié)構(gòu)提供一個統(tǒng)一的接口,從而支持同樣的算法在不同的集合結(jié)構(gòu)上進(jìn)行操作

總結(jié)

對于集合內(nèi)部結(jié)果常常變化各異,不想暴露其內(nèi)部結(jié)構(gòu)的話,但又想讓客戶代碼透明的訪問其中的元素,可以使用迭代器模式

## 橋接模式

> 橋接模式(Bridge)將抽象部分與它的實現(xiàn)部分分離,使它們都可以獨立地變化

```javascript

class Color {

? ? constructor(name){

? ? ? ? this.name = name

? ? }

}

class Shape {

? ? constructor(name,color){

? ? ? ? this.name = name

? ? ? ? this.color = color

? ? }

? ? draw(){

? ? ? ? console.log(`${this.color.name} ${this.name}`)

? ? }

}

//測試

let red = new Color('red')

let yellow = new Color('yellow')

let circle = new Shape('circle', red)

circle.draw()

let triangle = new Shape('triangle', yellow)

triangle.draw()

```

**優(yōu)點**

有助于獨立地管理各組成部分, 把抽象化與實現(xiàn)化解耦

提高可擴充性

**缺點**

大量的類將導(dǎo)致開發(fā)成本的增加,同時在性能方面可能也會有所減少。

## 組合模式

> 將對象組合成樹形結(jié)構(gòu),以表示“整體-部分”的層次結(jié)構(gòu)。 通過對象的多態(tài)表現(xiàn),使得用戶對單個對象和組合對象的使用具有一致性。

```javascript

class TrainOrder {

create () {

console.log('創(chuàng)建火車票訂單')

}

}

class HotelOrder {

create () {

console.log('創(chuàng)建酒店訂單')

}

}

class TotalOrder {

constructor () {

this.orderList = []

}

addOrder (order) {

this.orderList.push(order)

return this

}

create () {

this.orderList.forEach(item => {

item.create()

})

return this

}

}

// 可以在購票網(wǎng)站買車票同時也訂房間

let train = new TrainOrder()

let hotel = new HotelOrder()

let total = new TotalOrder()

total.addOrder(train).addOrder(hotel).create()

```

**場景**

表示對象-整體層次結(jié)構(gòu)

希望用戶忽略組合對象和單個對象的不同,用戶將統(tǒng)一地使用組合結(jié)構(gòu)中的所有對象(方法)

**缺點**

如果通過組合模式創(chuàng)建了太多的對象,那么這些對象可能會讓系統(tǒng)負(fù)擔(dān)不起。

## 原型模式

> 原型模式(prototype)是指用原型實例指向創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象。

```javascript

```javascript

class Person {

? constructor(name) {

? ? this.name = name

? }

? getName() {

? ? return this.name

? }

}

class Student extends Person {

? constructor(name) {

? ? super(name)

? }

? sayHello() {

? ? console.log(`Hello, My name is ${this.name}`)

? }

}

let student = new Student("xiaoming")

student.sayHello()

```

## 原型模式

> ,就是創(chuàng)建一個共享的原型,通過拷貝這個原型來創(chuàng)建新的類,用于創(chuàng)建重復(fù)的對象,帶來性能上的提升。

## 策略模式

> 定義一系列的算法,把它們一個個封裝起來,并且使它們可以互相替換

```javascript

本例來自掘金九思

<html>

<head>

? ? <title>策略模式-校驗表單</title>

? ? <meta content="text/html; charset=utf-8" http-equiv="Content-Type">

</head>

<body>

? ? <form id = "registerForm" method="post" action="http://xxxx.com/api/register">

? ? ? ? 用戶名:<input type="text" name="userName">

? ? ? ? 密碼:<input type="text" name="password">

? ? ? ? 手機號碼:<input type="text" name="phoneNumber">

? ? ? ? <button type="submit">提交</button>

? ? </form>

? ? <script type="text/javascript">

? ? ? ? // 策略對象

? ? ? ? const strategies = {

? ? ? ? ? isNoEmpty: function (value, errorMsg) {

? ? ? ? ? ? if (value === '') {

? ? ? ? ? ? ? return errorMsg;

? ? ? ? ? ? }

? ? ? ? ? },

? ? ? ? ? isNoSpace: function (value, errorMsg) {

? ? ? ? ? ? if (value.trim() === '') {

? ? ? ? ? ? ? return errorMsg;

? ? ? ? ? ? }

? ? ? ? ? },

? ? ? ? ? minLength: function (value, length, errorMsg) {

? ? ? ? ? ? if (value.trim().length < length) {

? ? ? ? ? ? ? return errorMsg;

? ? ? ? ? ? }

? ? ? ? ? },

? ? ? ? ? maxLength: function (value, length, errorMsg) {

? ? ? ? ? ? if (value.length > length) {

? ? ? ? ? ? ? return errorMsg;

? ? ? ? ? ? }

? ? ? ? ? },

? ? ? ? ? isMobile: function (value, errorMsg) {

? ? ? ? ? ? if (!/^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[7]|18[0|1|2|3|5|6|7|8|9])\d{8}$/.test(value)) {

? ? ? ? ? ? ? return errorMsg;

? ? ? ? ? ? }? ? ? ? ? ? ? ?

? ? ? ? ? }

? ? ? ? }


? ? ? ? // 驗證類

? ? ? ? class Validator {

? ? ? ? ? constructor() {

? ? ? ? ? ? this.cache = []

? ? ? ? ? }

? ? ? ? ? add(dom, rules) {

? ? ? ? ? ? for(let i = 0, rule; rule = rules[i++];) {

? ? ? ? ? ? ? let strategyAry = rule.strategy.split(':')

? ? ? ? ? ? ? let errorMsg = rule.errorMsg

? ? ? ? ? ? ? this.cache.push(() => {

? ? ? ? ? ? ? ? let strategy = strategyAry.shift()

? ? ? ? ? ? ? ? strategyAry.unshift(dom.value)

? ? ? ? ? ? ? ? strategyAry.push(errorMsg)

? ? ? ? ? ? ? ? return strategies[strategy].apply(dom, strategyAry)

? ? ? ? ? ? ? })

? ? ? ? ? ? }

? ? ? ? ? }

? ? ? ? ? start() {

? ? ? ? ? ? for(let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {

? ? ? ? ? ? ? let errorMsg = validatorFunc()

? ? ? ? ? ? ? if (errorMsg) {

? ? ? ? ? ? ? ? return errorMsg

? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? }

? ? ? ? }

? ? ? ? // 調(diào)用代碼

? ? ? ? let registerForm = document.getElementById('registerForm')

? ? ? ? let validataFunc = function() {

? ? ? ? ? let validator = new Validator()

? ? ? ? ? validator.add(registerForm.userName, [{

? ? ? ? ? ? strategy: 'isNoEmpty',

? ? ? ? ? ? errorMsg: '用戶名不可為空'

? ? ? ? ? }, {

? ? ? ? ? ? strategy: 'isNoSpace',

? ? ? ? ? ? errorMsg: '不允許以空白字符命名'

? ? ? ? ? }, {

? ? ? ? ? ? strategy: 'minLength:2',

? ? ? ? ? ? errorMsg: '用戶名長度不能小于2位'

? ? ? ? ? }])

? ? ? ? ? validator.add(registerForm.password, [ {

? ? ? ? ? ? strategy: 'minLength:6',

? ? ? ? ? ? errorMsg: '密碼長度不能小于6位'

? ? ? ? ? }])

? ? ? ? ? validator.add(registerForm.phoneNumber, [{

? ? ? ? ? ? strategy: 'isMobile',

? ? ? ? ? ? errorMsg: '請輸入正確的手機號碼格式'

? ? ? ? ? }])

? ? ? ? ? return validator.start()

? ? ? ? }

? ? ? ? registerForm.onsubmit = function() {

? ? ? ? ? let errorMsg = validataFunc()

? ? ? ? ? if (errorMsg) {

? ? ? ? ? ? alert(errorMsg)

? ? ? ? ? ? return false

? ? ? ? ? }

? ? ? ? }

? ? </script>

</body>

</html>

```

**場景例子**

如果在一個系統(tǒng)里面有許多類,它們之間的區(qū)別僅在于它們的'行為',那么使用策略模式可以動態(tài)地讓一個對象在許多行為中選擇一種行為。

一個系統(tǒng)需要動態(tài)地在幾種算法中選擇一種。

表單驗證

**優(yōu)點**

利用組合、委托、多態(tài)等技術(shù)和思想,可以有效的避免多重條件選擇語句

提供了對開放-封閉原則的完美支持,將算法封裝在獨立的strategy中,使得它們易于切換,理解,易于擴展

利用組合和委托來讓Context擁有執(zhí)行算法的能力,這也是繼承的一種更輕便的代替方案

**缺點**

會在程序中增加許多策略類或者策略對象

要使用策略模式,必須了解所有的strategy,必須了解各個strategy之間的不同點,這樣才能選擇一個合適的strategy

## 享元模式

> 運用共享技術(shù)有效地支持大量細(xì)粒度對象的復(fù)用。系統(tǒng)只使用少量的對象,而這些對象都很相似,狀態(tài)變化很小,可以實現(xiàn)對象的多次復(fù)用。由于享元模式要求能夠共享的對象必須是細(xì)粒度對象,因此它又稱為輕量級模式,它是一種對象結(jié)構(gòu)型模式

```javascript

本例來自掘金九思

let examCarNum = 0? ? ? ? // 駕考車總數(shù)

/* 駕考車對象 */

class ExamCar {

? ? constructor(carType) {

? ? ? ? examCarNum++

? ? ? ? this.carId = examCarNum

? ? ? ? this.carType = carType ? '手動檔' : '自動檔'

? ? ? ? this.usingState = false? ? // 是否正在使用

? ? }

? ? /* 在本車上考試 */

? ? examine(candidateId) {

? ? ? ? return new Promise((resolve => {

? ? ? ? ? ? this.usingState = true

? ? ? ? ? ? console.log(`考生- ${ candidateId } 開始在${ this.carType }駕考車- ${ this.carId } 上考試`)

? ? ? ? ? ? setTimeout(() => {

? ? ? ? ? ? ? ? this.usingState = false

? ? ? ? ? ? ? ? console.log(`%c考生- ${ candidateId } 在${ this.carType }駕考車- ${ this.carId } 上考試完畢`, 'color:#f40')

? ? ? ? ? ? ? ? resolve()? ? ? ? ? ? ? ? ? ? ? // 0~2秒后考試完畢

? ? ? ? ? ? }, Math.random() * 2000)

? ? ? ? }))

? ? }

}

/* 手動檔汽車對象池 */

ManualExamCarPool = {

? ? _pool: [],? ? ? ? ? ? ? ? ? // 駕考車對象池

? ? _candidateQueue: [],? ? ? ? // 考生隊列

? ? /* 注冊考生 ID 列表 */

? ? registCandidates(candidateList) {

? ? ? ? candidateList.forEach(candidateId => this.registCandidate(candidateId))

? ? },

? ? /* 注冊手動檔考生 */

? ? registCandidate(candidateId) {

? ? ? ? const examCar = this.getManualExamCar()? ? // 找一個未被占用的手動檔駕考車

? ? ? ? if (examCar) {

? ? ? ? ? ? examCar.examine(candidateId)? ? ? ? ? // 開始考試,考完了讓隊列中的下一個考生開始考試

? ? ? ? ? ? ? .then(() => {

? ? ? ? ? ? ? ? ? const nextCandidateId = this._candidateQueue.length && this._candidateQueue.shift()

? ? ? ? ? ? ? ? ? nextCandidateId && this.registCandidate(nextCandidateId)

? ? ? ? ? ? ? })

? ? ? ? } else this._candidateQueue.push(candidateId)

? ? },

? ? /* 注冊手動檔車 */

? ? initManualExamCar(manualExamCarNum) {

? ? ? ? for (let i = 1; i <= manualExamCarNum; i++) {

? ? ? ? ? ? this._pool.push(new ExamCar(true))

? ? ? ? }

? ? },

? ? /* 獲取狀態(tài)為未被占用的手動檔車 */

? ? getManualExamCar() {

? ? ? ? return this._pool.find(car => !car.usingState)

? ? }

}

ManualExamCarPool.initManualExamCar(3)? ? ? ? ? // 一共有3個駕考車

ManualExamCarPool.registCandidates([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])? // 10個考生來考試

```

**場景例子**

文件上傳需要創(chuàng)建多個文件實例的時候

如果一個應(yīng)用程序使用了大量的對象,而這些大量的對象造成了很大的存儲開銷時就應(yīng)該考慮使用享元模式

**優(yōu)點**

大大減少對象的創(chuàng)建,降低系統(tǒng)的內(nèi)存,使效率提高。

**缺點**

提高了系統(tǒng)的復(fù)雜度,需要分離出外部狀態(tài)和內(nèi)部狀態(tài),而且外部狀態(tài)具有固有化的性質(zhì), 不應(yīng)該隨著內(nèi)部狀態(tài)的變化而變化,否則會造成系統(tǒng)的混亂

## 模板方法模式

> 模板方法模式由兩部分結(jié)構(gòu)組成,第一部分是抽象父類,第二部分是具體的實現(xiàn)子類。通常在抽象父類中封裝了子類的算法框架,包括實現(xiàn)一些公共方法和封裝子類中所有方法的執(zhí)行順序。子類通過繼承這個抽象類,也繼承了整個算法結(jié)構(gòu),并且可以選擇重寫父類的方法。

```javascript

//本例來及掘金九思

class Beverage {

? ? constructor({brewDrink, addCondiment}) {

? ? ? ? this.brewDrink = brewDrink

? ? ? ? this.addCondiment = addCondiment

? ? }

? ? /* 燒開水,共用方法 */

? ? boilWater() { console.log('水已經(jīng)煮沸=== 共用') }

? ? /* 倒杯子里,共用方法 */

? ? pourCup() { console.log('倒進(jìn)杯子里===共用') }

? ? /* 模板方法 */

? ? init() {

? ? ? ? this.boilWater()

? ? ? ? this.brewDrink()

? ? ? ? this.pourCup()

? ? ? ? this.addCondiment()

? ? }

}

/* 咖啡 */

const coffee = new Beverage({

? ? /* 沖泡咖啡,覆蓋抽象方法 */

? ? brewDrink: function() { console.log('沖泡咖啡') },

? ? /* 加調(diào)味品,覆蓋抽象方法 */

? ? addCondiment: function() { console.log('加點奶和糖') }

})

coffee.init()

```

**場景例子**

一次性實現(xiàn)一個算法的不變的部分,并將可變的行為留給子類來實現(xiàn)

子類中公共的行為應(yīng)被提取出來并集中到一個公共父類中的避免代碼重復(fù)

**優(yōu)點**

提取了公共代碼部分,易于維護(hù)

**缺點**

增加了系統(tǒng)復(fù)雜度,主要是增加了的抽象類和類間聯(lián)系

## 職責(zé)鏈模式

使多個對象都有機會處理請求,從而避免請求的發(fā)送者和接受者之間的耦合關(guān)系,將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它為止

```javascript

// 請假審批,需要組長審批、經(jīng)理審批、總監(jiān)審批

class Action {

? ? constructor(name) {

? ? ? ? this.name = name

? ? ? ? this.nextAction = null

? ? }

? ? setNextAction(action) {

? ? ? ? this.nextAction = action

? ? }

? ? handle() {

? ? ? ? console.log( `${this.name} 審批`)

? ? ? ? if (this.nextAction != null) {

? ? ? ? ? ? this.nextAction.handle()

? ? ? ? }

? ? }

}

let a1 = new Action("組長")

let a2 = new Action("經(jīng)理")

let a3 = new Action("總監(jiān)")

a1.setNextAction(a2)

a2.setNextAction(a3)

a1.handle()

```

**場景例子**

JS 中的事件冒泡

作用域鏈

原型鏈

**優(yōu)點**

降低耦合度。它將請求的發(fā)送者和接收者解耦。

簡化了對象。使得對象不需要知道鏈的結(jié)構(gòu)

增強給對象指派職責(zé)的靈活性。通過改變鏈內(nèi)的成員或者調(diào)動它們的次序,允許動態(tài)地新增或者刪除責(zé)任

增加新的請求處理類很方便。

**缺點**

不能保證某個請求一定會被鏈中的節(jié)點處理,這種情況可以在鏈尾增加一個保底的接受者節(jié)點來處理這種即將離開鏈尾的請求。

使程序中多了很多節(jié)點對象,可能再一次請求的過程中,大部分的節(jié)點并沒有起到實質(zhì)性的作用。他們的作用僅僅是讓請求傳遞下去,從性能當(dāng)面考慮,要避免過長的職責(zé)鏈到來的性能損耗。

## 命令模式

> 將一個請求封裝成一個對象,從而讓你使用不同的請求把客戶端參數(shù)化,對請求排隊或者記錄請求日志,可以提供命令的撤銷和恢復(fù)功能。

```javascript

// 接收者類

class Receiver {

? ? execute() {

? ? ? console.log('接收者執(zhí)行請求')

? ? }

? }


// 命令者

class Command {?

? ? constructor(receiver) {

? ? ? ? this.receiver = receiver

? ? }

? ? execute () {? ?

? ? ? ? console.log('命令');

? ? ? ? this.receiver.execute()

? ? }

}

// 觸發(fā)者

class Invoker {?

? ? constructor(command) {

? ? ? ? this.command = command

? ? }

? ? invoke() {?

? ? ? ? console.log('開始')

? ? ? ? this.command.execute()

? ? }

}


```javascript

// 倉庫

const warehouse = new Receiver();?

// 訂單? ?

const order = new Command(warehouse);?

// 客戶

const client = new Invoker(order);? ? ?

client.invoke()

```

**優(yōu)點**

對命令進(jìn)行封裝,使命令易于擴展和修改

命令發(fā)出者和接受者解耦,使發(fā)出者不需要知道命令的具體執(zhí)行過程即可執(zhí)行

**缺點**

使用命令模式可能會導(dǎo)致某些系統(tǒng)有過多的具體命令類。

備忘錄模式

在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài)。這樣以后就可將該對象恢復(fù)到保存的狀態(tài)。

```javascript

//備忘類

class Memento{

? ? constructor(content){

? ? ? ? this.content = content

? ? }

? ? getContent(){

? ? ? ? return this.content

? ? }

}

// 備忘列表

class CareTaker {

? ? constructor(){

? ? ? ? this.list = []

? ? }

? ? add(memento){

? ? ? ? this.list.push(memento)

? ? }

? ? get(index){

? ? ? ? return this.list[index]

? ? }

}

// 編輯器

class Editor {

? ? constructor(){

? ? ? ? this.content = null

? ? }

? ? setContent(content){

? ? ? ? this.content = content

? ? }

? ? getContent(){

? ? return this.content

? ? }

? ? saveContentToMemento(){

? ? ? ? return new Memento(this.content)

? ? }

? ? getContentFromMemento(memento){

? ? ? ? this.content = memento.getContent()

? ? }

}

//測試代碼

let editor = new Editor()

let careTaker = new CareTaker()

editor.setContent('111')

editor.setContent('222')

careTaker.add(editor.saveContentToMemento())

editor.setContent('333')

careTaker.add(editor.saveContentToMemento())

editor.setContent('444')

console.log(editor.getContent()) //444

editor.getContentFromMemento(careTaker.get(1))

console.log(editor.getContent()) //333

editor.getContentFromMemento(careTaker.get(0))

console.log(editor.getContent()) //222

```

**場景例子**

分頁控件

撤銷組件

**優(yōu)點**

給用戶提供了一種可以恢復(fù)狀態(tài)的機制,可以使用戶能夠比較方便地回到某個歷史的狀態(tài)

**缺點**

消耗資源。如果類的成員變量過多,勢必會占用比較大的資源,而且每一次保存都會消耗一定的內(nèi)存。

## 中介者模式

> 解除對象與對象之間的緊耦合關(guān)系。增加一個中介者對象后,所有的

> 相關(guān)對象都通過中介者對象來通信,而不是互相引用,所以當(dāng)一個對象發(fā)生改變時,只需要通知

> 中介者對象即可。中介者使各對象之間耦合松散,而且可以獨立地改變它們之間的交互。中介者

> 模式使網(wǎng)狀的多對多關(guān)系變成了相對簡單的一對多關(guān)系(類似于觀察者模式,但是單向的,由中介者統(tǒng)一管理。)

```javascript

class A {

? ? constructor() {

? ? ? ? this.number = 0

? ? }

? ? setNumber(num, m) {

? ? ? ? this.number = num

? ? ? ? if (m) {

? ? ? ? ? ? m.setB()

? ? ? ? }

? ? }

}

class B {

? ? constructor() {

? ? ? ? this.number = 0

? ? }

? ? setNumber(num, m) {

? ? ? ? this.number = num

? ? ? ? if (m) {

? ? ? ? ? ? m.setA()

? ? ? ? }

? ? }

}

class Mediator {

? ? constructor(a, b) {

? ? ? ? this.a = a

? ? ? ? this.b = b

? ? }

? ? setA() {

? ? ? ? let number = this.b.number

? ? ? ? this.a.setNumber(number * 10)

? ? }

? ? setB() {

? ? ? ? let number = this.a.number

? ? ? ? this.b.setNumber(number / 10)

? ? }

}

let a = new A()

let b = new B()

let m = new Mediator(a, b)

a.setNumber(10, m)

console.log(a.number, b.number)

b.setNumber(10, m)

console.log(a.number, b.number)

```

**場景例子**

系統(tǒng)中對象之間存在比較復(fù)雜的引用關(guān)系,導(dǎo)致它們之間的依賴關(guān)系結(jié)構(gòu)混亂而且難以復(fù)用該對象

想通過一個中間類來封裝多個類中的行為,而又不想生成太多的子類。

**優(yōu)點**

使各對象之間耦合松散,而且可以獨立地改變它們之間的交互

中介者和對象一對多的關(guān)系取代了對象之間的網(wǎng)狀多對多的關(guān)系

如果對象之間的復(fù)雜耦合度導(dǎo)致維護(hù)很困難,而且耦合度隨項目變化增速很快,就需要中介者重構(gòu)代碼

**缺點**

系統(tǒng)中會新增一個中介者對象,因 為對象之間交互的復(fù)雜性,轉(zhuǎn)移成了中介者對象的復(fù)雜性,使得中介者對象經(jīng)常是巨大的。中介 者對象自身往往就是一個難以維護(hù)的對象。

## 解釋器模式

> 給定一個語言, 定義它的文法的一種表示,并定義一個解釋器, 該解釋器使用該表示來解釋語言中的句子。

```javascript

此例來自心譚博客

class Context {

? ? constructor() {

? ? ? this._list = []; // 存放 終結(jié)符表達(dá)式

? ? ? this._sum = 0; // 存放 非終結(jié)符表達(dá)式(運算結(jié)果)

? ? }


? ? get sum() {

? ? ? return this._sum;

? ? }

? ? set sum(newValue) {

? ? ? this._sum = newValue;

? ? }

? ? add(expression) {

? ? ? this._list.push(expression);

? ? }

? ? get list() {

? ? ? return [...this._list];

? ? }

? }


? class PlusExpression {

? ? interpret(context) {

? ? ? if (!(context instanceof Context)) {

? ? ? ? throw new Error("TypeError");

? ? ? }

? ? ? context.sum = ++context.sum;

? ? }

? }

? class MinusExpression {

? ? interpret(context) {

? ? ? if (!(context instanceof Context)) {

? ? ? ? throw new Error("TypeError");

? ? ? }

? ? ? context.sum = --context.sum;

? ? }

? }


? /** 以下是測試代碼 **/

? const context = new Context();


? // 依次添加: 加法 | 加法 | 減法 表達(dá)式

? context.add(new PlusExpression());

? context.add(new PlusExpression());

? context.add(new MinusExpression());


? // 依次執(zhí)行: 加法 | 加法 | 減法 表達(dá)式

? context.list.forEach(expression => expression.interpret(context));

? console.log(context.sum);

```

**優(yōu)點**

易于改變和擴展文法。

由于在解釋器模式中使用類來表示語言的文法規(guī)則,因此可以通過繼承等機制來改變或擴展文法

**缺點**

執(zhí)行效率較低,在解釋器模式中使用了大量的循環(huán)和遞歸調(diào)用,因此在解釋較為復(fù)雜的句子時其速度慢

對于復(fù)雜的文法比較難維護(hù)

## 訪問者模式

> 表示一個作用于某對象結(jié)構(gòu)中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作

```javascript

// 訪問者?

class Visitor {

? ? constructor() {}

? ? visitConcreteElement(ConcreteElement) {

? ? ? ? ConcreteElement.operation()

? ? }

}

// 元素類?

class ConcreteElement{

? ? constructor() {

? ? }

? ? operation() {

? ? ? console.log("ConcreteElement.operation invoked");?

? ? }

? ? accept(visitor) {

? ? ? ? visitor.visitConcreteElement(this)

? ? }

}

// client

let visitor = new Visitor()

let element = new ConcreteElement()

elementA.accept(visitor)

```

**場景例子**

對象結(jié)構(gòu)中對象對應(yīng)的類很少改變,但經(jīng)常需要在此對象結(jié)構(gòu)上定義新的操作

需要對一個對象結(jié)構(gòu)中的對象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對象的類,也不希望在增加新操作時修改這些類。

**優(yōu)點**

符合單一職責(zé)原則

優(yōu)秀的擴展性

靈活性

**缺點**

具體元素對訪問者公布細(xì)節(jié),違反了迪米特原則

違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。

具體元素變更比較困難

兄臺,如果對你有所幫助,請點個贊也是給予我的支持

**參考資料**

掘金九思的設(shè)計模式

雙越-Javascript設(shè)計模式系統(tǒng)講解與應(yīng)用

JavaScript設(shè)計模式與開發(fā)實踐

湯姆大叔的設(shè)計模式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容