JavaScript設計模式es6(23種)

終於寫完了,寫了一個多月,每一種模式都是查閱各類資料總結出,本文較長,但願對你有所幫助,若是對你有用,請點贊支持一把,也是給予我寫做的動力javascript

設計模式簡介

設計模式表明了最佳的實踐,一般被有經驗的面向對象的軟件開發人員所採用。設計模式是軟件開發人員在軟件開發過程當中面臨的通常問題的解決方案。這些解決方案是衆多軟件開發人員通過至關長的一段時間的試驗和錯誤總結出來的。html

設計模式是一套被反覆使用的、多數人知曉的、通過分類編目的、代碼設計經驗的總結。使用設計模式是爲了重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石同樣。vue

設計模式原則

  • S – Single Responsibility Principle 單一職責原則
    • 一個程序只作好一件事
    • 若是功能過於複雜就拆分開,每一個部分保持獨立
  • O – OpenClosed Principle 開放/封閉原則
    • 對擴展開放,對修改封閉
    • 增長需求時,擴展新代碼,而非修改已有代碼
  • L – Liskov Substitution Principle 里氏替換原則
    • 子類能覆蓋父類
    • 父類能出現的地方子類就能出現
  • I – Interface Segregation Principle 接口隔離原則
    • 保持接口的單一獨立
    • 相似單一職責原則,這裏更關注接口
  • D – Dependency Inversion Principle 依賴倒轉原則
    • 面向接口編程,依賴於抽象而不依賴於具體
    • 使用方只關注接口而不關注具體類的實現
SO體現較多,舉個栗子:(好比Promise)
  • 單一職責原則:每一個then中的邏輯只作好一件事
  • 開放封閉原則(對擴展開放,對修改封閉):若是新增需求,擴展then
再舉個栗子:(此例來源-守候-改善代碼的各方面問題)
//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;
    }
}

複製代碼

有如下兩個問題:java

  • 若是想添加其餘規則就得在函數裏面增長 case 。添加一個規則就修改一次!這樣違反了開放-封閉原則(對擴展開放,對修改關閉)。並且這樣也會致使整個 API 變得臃腫,難維護。
  • 好比A頁面須要添加一個金額的校驗,B頁面須要一個日期的校驗,可是金額的校驗只在A頁面須要,日期的校驗只在B頁面須要。若是一直添加 case 。就是致使A頁面把只在B頁面須要的校驗規則也添加進去,形成沒必要要的開銷。B頁面也同理。

建議的方式是給這個 API 增長一個擴展的接口:es6

let checkType=(function(){
    let rules={
        email(str){
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        },
        mobile(str){
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        }
    };
    //暴露接口
    return {
        //校驗
        check(str, type){
            return rules[type]?rules[type](str):false;
        },
        //添加規則
        addRule(type,fn){
            rules[type]=fn;
        }
    }
})();

//調用方式
//使用mobile校驗規則
console.log(checkType.check('188170239','mobile'));
//添加金額校驗規則
checkType.addRule('money',function (str) {
    return /^[0-9]+(.[0-9]{2})?$/.test(str)
});
//使用金額校驗規則
console.log(checkType.check('18.36','money'));

複製代碼

此例更詳細內容請查看-> 守候i-重構-改善代碼的各方面問題ajax

設計模式分類(23種設計模式)

  • 建立型
    • 單例模式
    • 原型模式
    • 工廠模式
    • 抽象工廠模式
    • 建造者模式
  • 結構型
    • 適配器模式
    • 裝飾器模式
    • 代理模式
    • 外觀模式
    • 橋接模式
    • 組合模式
    • 享元模式
  • 行爲型
    • 觀察者模式
    • 迭代器模式
    • 策略模式
    • 模板方法模式
    • 職責鏈模式
    • 命令模式
    • 備忘錄模式
    • 狀態模式
    • 訪問者模式
    • 中介者模式
    • 解釋器模式

工廠模式

工廠模式定義一個用於建立對象的接口,這個接口由子類決定實例化哪個類。該模式使一個類的實例化延遲到了子類。而子類能夠重寫接口方法以便建立的時候指定本身的對象類型。算法

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()

複製代碼
適用場景
  • 若是你不想讓某個子系統與較大的那個對象之間造成強耦合,而是想運行時從許多子系統中進行挑選的話,那麼工廠模式是一個理想的選擇
  • 將new操做簡單封裝,遇到new的時候就應該考慮是否用工廠模式;
  • 須要依賴具體環境建立不一樣實例,這些實例都有相同的行爲,這時候咱們可使用工廠模式,簡化實現的過程,同時也能夠減小每種對象所需的代碼量,有利於消除對象間的耦合,提供更大的靈活性
優勢
  • 建立對象的過程可能很複雜,但咱們只須要關心建立結果。
  • 構造函數和建立者分離, 符合「開閉原則」
  • 一個調用者想建立一個對象,只要知道其名稱就能夠了。
  • 擴展性高,若是想增長一個產品,只要擴展一個工廠類就能夠。
缺點
  • 添加新產品時,須要編寫新的具體產品類,必定程度上增長了系統的複雜度
  • 考慮到系統的可擴展性,須要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增長了系統的抽象性和理解難度
何時不用

當被應用到錯誤的問題類型上時,這一模式會給應用程序引入大量沒必要要的複雜性.除非爲建立對象提供一個接口是咱們編寫的庫或者框架的一個設計上目標,不然我會建議使用明確的構造器,以免沒必要要的開銷。vuex

因爲對象的建立過程被高效的抽象在一個接口後面的事實,這也會給依賴於這個過程可能會有多複雜的單元測試帶來問題。express

例子
  • 曾經咱們熟悉的JQuery的$()就是一個工廠函數,它根據傳入參數的不一樣建立元素或者去尋找上下文中的元素,建立成相應的jQuery對象
class jQuery {
    constructor(selector) {
        super(selector)
    }
    add() {
        
    }
  // 此處省略若干API
}

window.$ = function(selector) {
    return new jQuery(selector)
}


複製代碼
  • vue 的異步組件

在大型應用中,咱們可能須要將應用分割成小一些的代碼塊,而且只在須要的時候才從服務器加載一個模塊。爲了簡化,Vue 容許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue 只有在這個組件須要被渲染的時候纔會觸發該工廠函數,且會把結果緩存起來供將來重渲染。例如:編程

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回調傳遞組件定義
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

複製代碼

單例模式

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

class LoginForm {
    constructor() {
        this.state = 'hide'
    }
    show() {
        if (this.state === 'show') {
            alert('已經顯示')
            return
        }
        this.state = 'show'
        console.log('登陸框顯示成功')
    }
    hide() {
        if (this.state === 'hide') {
            alert('已經隱藏')
            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)
複製代碼

優勢

  • 劃分命名空間,減小全局變量
  • 加強模塊性,把本身的代碼組織在一個全局變量名下,放在單一位置,便於維護
  • 且只會實例化一次。簡化了代碼的調試和維護

缺點

  • 因爲單例模式提供的是一種單點訪問,因此它有可能致使模塊間的強耦合 從而不利於單元測試。沒法單獨測試一個調用了來自單例的方法的類,而只能把它與那個單例做爲一個單元一塊兒測試。

場景例子

  • 定義命名空間和實現分支型方法
  • 登陸框
  • vuex 和 redux中的store

適配器模式

將一個類的接口轉化爲另一個接口,以知足用戶需求,使類之間接口不兼容問題經過適配器得以解決。

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充電頭 適配器轉Type-c充電頭
複製代碼

優勢

  • 可讓任何兩個沒有關聯的類一塊兒運行。
  • 提升了類的複用。
  • 適配對象,適配庫,適配數據

缺點

  • 額外對象的建立,非直接調用,存在必定的開銷(且不像代理模式在某些功能點上可實現性能優化)
  • 若是不必使用適配器模式的話,能夠考慮重構,若是使用的話,儘可能把文檔完善

場景

  • 整合第三方SDK
  • 封裝舊接口
// 本身封裝的ajax, 使用方式以下
ajax({
    url: '/getData',
    type: 'Post',
    dataType: 'json',
    data: {
        test: 111
    }
}).done(function() {})
// 由於歷史緣由,代碼中全都是:
// $.ajax({....})

// 作一層適配器
var $ = {
    ajax: function (options) {
        return ajax(options)
    }
}
複製代碼
  • vue的computed
<template>
    <div id="example">
        <p>Original message: "{{ message }}"</p>  <!-- Hello -->
        <p>Computed reversed message: "{{ reversedMessage }}"</p>  <!-- olleH -->
    </div>
</template>
<script type='text/javascript'>
    export default {
        name: 'demo',
        data() {
            return {
                message: 'Hello'
            }
        },
        computed: {
            reversedMessage: function() {
                return this.message.split('').reverse().join('')
            }
        }
    }
</script>

複製代碼
原有data 中的數據不知足當前的要求,經過計算屬性的規則來適配成咱們須要的格式,對原有數據並無改變,只改變了原有數據的表現形式

不一樣點

適配器與代理模式類似

  • 適配器模式: 提供一個不一樣的接口(如不一樣版本的插頭)
  • 代理模式: 提供如出一轍的接口

裝飾者模式

  • 動態地給某個對象添加一些額外的職責,,是一種實現繼承的替代方案
  • 在不改變原對象的基礎上,經過對其進行包裝擴展,使原有對象能夠知足用戶的更復雜需求,而不會影響從這個類中派生的其餘對象
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()
複製代碼

場景例子

  • 好比如今有4 種型號的自行車,咱們爲每種自行車都定義了一個單 獨的類。如今要給每種自行車都裝上前燈、尾 燈和鈴鐺這3 種配件。若是使用繼承的方式來給 每種自行車建立子類,則須要 4×3 = 12 個子類。 可是若是把前燈、尾燈、鈴鐺這些對象動態組 合到自行車上面,則只須要額外增長3 個類
  • ES7 Decorator 阮一峯
  • core-decorators

優勢

  • 裝飾類和被裝飾類都只關心自身的核心業務,實現瞭解耦。
  • 方便動態的擴展功能,且提供了比繼承更多的靈活性。

缺點

  • 多層裝飾比較複雜。
  • 經常會引入許多小對象,看起來比較類似,實際功能截然不同,從而使得咱們的應用程序架構變得複雜起來

代理模式

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

假設當A 在心情好的時候收到花,小明表白成功的概率有 60%,而當A 在心情差的時候收到花,小明表白的成功率無限趨近於0。 小明跟A 剛剛認識兩天,還沒法辨別A 何時心情好。若是不合時宜地把花送給A,花 被直接扔掉的可能性很大,這束花但是小明吃了7 天泡麪換來的。 可是A 的朋友B 卻很瞭解A,因此小明只管把花交給B,B 會監聽A 的心情變化,而後選 擇A 心情好的時候把花轉交給A,代碼以下:

let Flower = function() {}
let xiaoming = {
  sendFlower: function(target) {
    let flower = new Flower()
    target.receiveFlower(flower)
  }
}
let B = {
  receiveFlower: function(flower) {
    A.listenGoodMood(function() {
      A.receiveFlower(flower)
    })
  }
}
let A = {
  receiveFlower: function(flower) {
    console.log('收到花'+ flower)
  },
  listenGoodMood: function(fn) {
    setTimeout(function() {
      fn()
    }, 1000)
  }
}
xiaoming.sendFlower(B)
複製代碼

場景

  • HTML元 素事件代理
<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>
複製代碼

優勢

  • 代理模式能將代理對象與被調用對象分離,下降了系統的耦合度。代理模式在客戶端和目標對象之間起到一箇中介做用,這樣能夠起到保護目標對象的做用
  • 代理對象能夠擴展目標對象的功能;經過修改代理對象就能夠了,符合開閉原則;

缺點

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

不一樣點

裝飾者模式實現上和代理模式相似

  • 裝飾者模式: 擴展功能,原有功能不變且可直接使用
  • 代理模式: 顯示原有功能,可是通過限制以後的

外觀模式

爲子系統的一組接口提供一個一致的界面,定義了一個高層接口,這個接口使子系統更加容易使用

  1. 兼容瀏覽器事件綁定
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
    }
}; 
複製代碼
  1. 封裝接口
let myEvent = {
    // ...
    stop: e => {
        e.stopPropagation();
        e.preventDefault();
    }
};
複製代碼

場景

  • 設計初期,應該要有意識地將不一樣的兩個層分離,好比經典的三層結構,在數據訪問層和業務邏輯層、業務邏輯層和表示層之間創建外觀Facade
  • 在開發階段,子系統每每由於不斷的重構演化而變得愈來愈複雜,增長外觀Facade能夠提供一個簡單的接口,減小他們之間的依賴。
  • 在維護一個遺留的大型系統時,可能這個系統已經很難維護了,這時候使用外觀Facade也是很是合適的,爲繫系統開發一個外觀Facade類,爲設計粗糙和高度複雜的遺留代碼提供比較清晰的接口,讓新系統和Facade對象交互,Facade與遺留代碼交互全部的複雜工做。

參考: 大話設計模式

優勢

  • 減小系統相互依賴。
  • 提升靈活性。
  • 提升了安全性

缺點

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

觀察者模式

定義了一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知全部的觀察者對象,使它們可以自動更新本身,當一個對象的改變須要同時改變其它對象,而且它不知道具體有多少對象須要改變的時候,就應該考慮使用觀察者模式。

  • 發佈 & 訂閱
  • 一對多
// 主題 保存狀態,狀態變化以後觸發全部觀察者對象
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)

s.setState(12)
複製代碼

場景

  • DOM事件
document.body.addEventListener('click', function() {
    console.log('hello world!');
});
document.body.click()
複製代碼
  • vue 響應式

優勢

  • 支持簡單的廣播通訊,自動通知全部已經訂閱過的對象
  • 目標對象與觀察者之間的抽象耦合關係能單獨擴展以及重用
  • 增長了靈活性
  • 觀察者模式所作的工做就是在解耦,讓耦合的雙方都依賴於抽象,而不是依賴於具體。從而使得各自的變化都不會影響到另外一邊的變化。

缺點

過分使用會致使對象與對象之間的聯繫弱化,會致使程序難以跟蹤維護和理解


狀態模式

容許一個對象在其內部狀態改變的時候改變它的行爲,對象看起來彷佛修改了它的類

// 狀態 (弱光、強光、關燈)
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())

// 關閉
strong.handle(context)
console.log(context.getState())
複製代碼

場景

  • 一個對象的行爲取決於它的狀態,而且它必須在運行時刻根據狀態改變它的行爲
  • 一個操做中含有大量的分支語句,並且這些分支語句依賴於該對象的狀態

優勢

  • 定義了狀態與行爲之間的關係,封裝在一個類裏,更直觀清晰,增改方便
  • 狀態與狀態間,行爲與行爲間彼此獨立互不干擾
  • 用對象代替字符串來記錄當前狀態,使得狀態的切換更加一目瞭然

缺點

  • 會在系統中定義許多狀態類
  • 邏輯分散

迭代器模式

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

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

特色

  • 訪問一個聚合對象的內容而無需暴露它的內部表示。
  • 爲遍歷不一樣的集合結構提供一個統一的接口,從而支持一樣的算法在不一樣的集合結構上進行操做

總結

對於集合內部結果經常變化各異,不想暴露其內部結構的話,但又想讓客戶代碼透明的訪問其中的元素,可使用迭代器模式


橋接模式

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

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()

複製代碼

優勢

  • 有助於獨立地管理各組成部分, 把抽象化與實現化解耦
  • 提升可擴充性

缺點

  • 大量的類將致使開發成本的增長,同時在性能方面可能也會有所減小。

組合模式

  • 將對象組合成樹形結構,以表示「總體-部分」的層次結構。
  • 經過對象的多態表現,使得用戶對單個對象和組合對象的使用具備一致性。
class TrainOrder {
	create () {
		console.log('建立火車票訂單')
	}
}
class HotelOrder {
	create () {
		console.log('建立酒店訂單')
	}
}

class TotalOrder {
	constructor () {
		this.orderList = []
	}
	addOrder (order) {
		this.orderList.push(order)
		return this
	}
	create () {
		this.orderList.forEach(item => {
			item.create()
		})
		return this
	}
}
// 能夠在購票網站買車票同時也訂房間
let train = new TrainOrder()
let hotel = new HotelOrder()
let total = new TotalOrder()
total.addOrder(train).addOrder(hotel).create()
複製代碼

場景

  • 表示對象-總體層次結構
  • 但願用戶忽略組合對象和單個對象的不一樣,用戶將統一地使用組合結構中的全部對象(方法)

缺點

若是經過組合模式建立了太多的對象,那麼這些對象可能會讓系統負擔不起。


原型模式

原型模式(prototype)是指用原型實例指向建立對象的種類,而且經過拷貝這些原型建立新的對象。

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()
複製代碼

原型模式,就是建立一個共享的原型,經過拷貝這個原型來建立新的類,用於建立重複的對象,帶來性能上的提高。


策略模式

定義一系列的算法,把它們一個個封裝起來,而且使它們能夠互相替換

<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
              }
            }
          }
        }

        // 調用代碼
        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>
複製代碼

場景例子

  • 若是在一個系統裏面有許多類,它們之間的區別僅在於它們的'行爲',那麼使用策略模式能夠動態地讓一個對象在許多行爲中選擇一種行爲。
  • 一個系統須要動態地在幾種算法中選擇一種。
  • 表單驗證

優勢

  • 利用組合、委託、多態等技術和思想,能夠有效的避免多重條件選擇語句
  • 提供了對開放-封閉原則的完美支持,將算法封裝在獨立的strategy中,使得它們易於切換,理解,易於擴展
  • 利用組合和委託來讓Context擁有執行算法的能力,這也是繼承的一種更輕便的代替方案

缺點

  • 會在程序中增長許多策略類或者策略對象
  • 要使用策略模式,必須瞭解全部的strategy,必須瞭解各個strategy之間的不一樣點,這樣才能選擇一個合適的strategy

享元模式

運用共享技術有效地支持大量細粒度對象的複用。系統只使用少許的對象,而這些對象都很類似,狀態變化很小,能夠實現對象的屢次複用。因爲享元模式要求可以共享的對象必須是細粒度對象,所以它又稱爲輕量級模式,它是一種對象結構型模式

let examCarNum = 0         // 駕考車總數
/* 駕考車對象 */
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))
        }
    },

    /* 獲取狀態爲未被佔用的手動檔車 */
    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個考生來考試
複製代碼

場景例子

  • 文件上傳須要建立多個文件實例的時候
  • 若是一個應用程序使用了大量的對象,而這些大量的對象形成了很大的存儲開銷時就應該考慮使用享元模式

優勢

  • 大大減小對象的建立,下降系統的內存,使效率提升。

缺點

  • 提升了系統的複雜度,須要分離出外部狀態和內部狀態,並且外部狀態具備固有化的性質, 不該該隨着內部狀態的變化而變化,不然會形成系統的混亂

模板方法模式

模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。一般在抽象父類中封裝了子類的算法框架,包括實現一些公共方法和封裝子類中全部方法的執行順序。子類經過繼承這個抽象類,也繼承了整個算法結構,而且能夠選擇重寫父類的方法。

class Beverage {
    constructor({brewDrink, addCondiment}) {
        this.brewDrink = brewDrink
        this.addCondiment = addCondiment
    }
    /* 燒開水,共用方法 */
    boilWater() { console.log('水已經煮沸=== 共用') }
    /* 倒杯子裏,共用方法 */
    pourCup() { console.log('倒進杯子裏===共用') }
    /* 模板方法 */
    init() {
        this.boilWater()
        this.brewDrink()
        this.pourCup()
        this.addCondiment()
    }
}
/* 咖啡 */
const coffee = new Beverage({
     /* 沖泡咖啡,覆蓋抽象方法 */
     brewDrink: function() { console.log('沖泡咖啡') },
     /* 加調味品,覆蓋抽象方法 */
     addCondiment: function() { console.log('加點奶和糖') }
})
coffee.init() 
複製代碼

場景例子

  • 一次性實現一個算法的不變的部分,並將可變的行爲留給子類來實現
  • 子類中公共的行爲應被提取出來並集中到一個公共父類中的避免代碼重複

優勢

  • 提取了公共代碼部分,易於維護

缺點

  • 增長了系統複雜度,主要是增長了的抽象類和類間聯繫

職責鏈模式

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

// 請假審批,須要組長審批、經理審批、總監審批
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("經理")
let a3 = new Action("總監")
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()
複製代碼

場景例子

  • JS 中的事件冒泡
  • 做用域鏈
  • 原型鏈

優勢

  • 下降耦合度。它將請求的發送者和接收者解耦。
  • 簡化了對象。使得對象不須要知道鏈的結構
  • 加強給對象指派職責的靈活性。經過改變鏈內的成員或者調動它們的次序,容許動態地新增或者刪除責任
  • 增長新的請求處理類很方便。

缺點

  • 不能保證某個請求必定會被鏈中的節點處理,這種狀況能夠在鏈尾增長一個保底的接受者節點來處理這種即將離開鏈尾的請求。
  • 使程序中多了不少節點對象,可能再一次請求的過程當中,大部分的節點並無起到實質性的做用。他們的做用僅僅是讓請求傳遞下去,從性能當面考慮,要避免過長的職責鏈到來的性能損耗。

命令模式

將一個請求封裝成一個對象,從而讓你使用不一樣的請求把客戶端參數化,對請求排隊或者記錄請求日誌,能夠提供命令的撤銷和恢復功能。

// 接收者類
class Receiver {
    execute() {
      console.log('接收者執行請求')
    }
  }
  
// 命令者
class Command {  
    constructor(receiver) {
        this.receiver = receiver
    }
    execute () {    
        console.log('命令');
        this.receiver.execute()
    }
}
// 觸發者
class Invoker {   
    constructor(command) {
        this.command = command
    }
    invoke() {   
        console.log('開始')
        this.command.execute()
    }
}
  
// 倉庫
const warehouse = new Receiver();   
// 訂單    
const order = new Command(warehouse);  
// 客戶
const client = new Invoker(order);      
client.invoke()
複製代碼

優勢

  • 對命令進行封裝,使命令易於擴展和修改
  • 命令發出者和接受者解耦,使發出者不須要知道命令的具體執行過程便可執行

缺點

  • 使用命令模式可能會致使某些系統有過多的具體命令類。

備忘錄模式

在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態。這樣之後就可將該對象恢復到保存的狀態。

//備忘類
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
複製代碼

場景例子

  • 分頁控件
  • 撤銷組件

優勢

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

缺點

  • 消耗資源。若是類的成員變量過多,勢必會佔用比較大的資源,並且每一次保存都會消耗必定的內存。

中介者模式

解除對象與對象之間的緊耦合關係。增長一箇中介者對象後,全部的 相關對象都經過中介者對象來通訊,而不是互相引用,因此當一個對象發生改變時,只須要通知 中介者對象便可。中介者使各對象之間耦合鬆散,並且能夠獨立地改變它們之間的交互。中介者 模式使網狀的多對多關係變成了相對簡單的一對多關係(相似於觀察者模式,可是單向的,由中介者統一管理。)

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)
複製代碼

場景例子

  • 系統中對象之間存在比較複雜的引用關係,致使它們之間的依賴關係結構混亂並且難以複用該對象
  • 想經過一箇中間類來封裝多個類中的行爲,而又不想生成太多的子類。

優勢

  • 使各對象之間耦合鬆散,並且能夠獨立地改變它們之間的交互
  • 中介者和對象一對多的關係取代了對象之間的網狀多對多的關係
  • 若是對象之間的複雜耦合度致使維護很困難,並且耦合度隨項目變化增速很快,就須要中介者重構代碼

缺點

  • 系統中會新增一箇中介者對象,因 爲對象之間交互的複雜性,轉移成了中介者對象的複雜性,使得中介者對象常常是巨大的。中介 者對象自身每每就是一個難以維護的對象。

解釋器模式

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

此例來自心譚博客

class Context {
    constructor() {
      this._list = []; // 存放 終結符表達式
      this._sum = 0; // 存放 非終結符表達式(運算結果)
    }
  
    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();
  
  // 依次添加: 加法 | 加法 | 減法 表達式
  context.add(new PlusExpression());
  context.add(new PlusExpression());
  context.add(new MinusExpression());
  
  // 依次執行: 加法 | 加法 | 減法 表達式
  context.list.forEach(expression => expression.interpret(context));
  console.log(context.sum);
複製代碼

優勢

  • 易於改變和擴展文法。
  • 因爲在解釋器模式中使用類來表示語言的文法規則,所以能夠經過繼承等機制來改變或擴展文法

缺點

  • 執行效率較低,在解釋器模式中使用了大量的循環和遞歸調用,所以在解釋較爲複雜的句子時其速度慢
  • 對於複雜的文法比較難維護

訪問者模式

表示一個做用於某對象結構中的各元素的操做。它使你能夠在不改變各元素的類的前提下定義做用於這些元素的新操做。

// 訪問者  
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)
複製代碼

場景例子

  • 對象結構中對象對應的類不多改變,但常常須要在此對象結構上定義新的操做
  • 須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做"污染"這些對象的類,也不但願在增長新操做時修改這些類。

優勢

  • 符合單一職責原則
  • 優秀的擴展性
  • 靈活性

缺點

  • 具體元素對訪問者公佈細節,違反了迪米特原則
  • 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。
  • 具體元素變動比較困難

兄臺,若是對你有所幫助,請點個贊也是給予個人支持

參考資料

相關文章
相關標籤/搜索