1. 程式人生 > 其它 >幾種常用JavaScript設計模式es6

幾種常用JavaScript設計模式es6

設計模式分類(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的時候就應該考慮是否用工廠模式;
  • 需要依賴具體環境建立不同例項,這些例項都有相同的行為,這時候我們可以使用工廠模式,簡化實現的過程,同時也可以減少每種物件所需的程式碼量,有利於消除物件間的耦合,提供更大的靈活性
優點
  • 建立物件的過程可能很複雜,但我們只需要關心建立結果。
  • 建構函式和建立者分離, 符合“開閉原則”
  • 一個呼叫者想建立一個物件,只要知道其名稱就可以了。
  • 擴充套件性高,如果想增加一個產品,只要擴充套件一個工廠類就可以。
缺點
  • 新增新產品時,需要編寫新的具體產品類,一定程度上增加了系統的複雜度
  • 考慮到系統的可擴充套件性,需要引入抽象層,在客戶端程式碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度
什麼時候不用

當被應用到錯誤的問題型別上時,這一模式會給應用程式引入大量不必要的複雜性.除非為建立物件提供一個介面是我們編寫的庫或者框架的一個設計上目標,否則我會建議使用明確的構造器,以避免不必要的開銷。

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

例子
  • 曾經我們熟悉的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>
  • 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 中的資料不滿足當前的要求,通過計算屬性的規則來適配成我們需要的格式,對原有資料並沒有改變,只改變了原有資料的表現形式

不同點

介面卡與代理模式相似

  • 介面卡模式: 提供一個不同的介面(如不同版本的插頭)
  • 代理模式: 提供一模一樣的介面

代理模式

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

假設當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>

優點

  • 代理模式能將代理物件與被呼叫物件分離,降低了系統的耦合度。代理模式在客戶端和目標物件之間起到一箇中介作用,這樣可以起到保護目標物件的作用
  • 代理物件可以擴充套件目標物件的功能;通過修改代理物件就可以了,符合開閉原則;

缺點

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

不同點

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

  • 裝飾者模式: 擴充套件功能,原有功能不變且可直接使用
  • 代理模式: 顯示原有功能,但是經過限制之後的

觀察者模式

定義了一種一對多的關係,讓多個觀察者物件同時監聽某一個主題物件,這個主題物件的狀態發生變化時就會通知所有的觀察者物件,使它們能夠自動更新自己,當一個物件的改變需要同時改變其它物件,並且它不知道具體有多少物件需要改變的時候,就應該考慮使用觀察者模式。

  • 釋出 & 訂閱
  • 一對多
// 主題 儲存狀態,狀態變化之後觸發所有觀察者物件
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 響應式

優點

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

缺點

過度使用會導致物件與物件之間的聯絡弱化,會導致程式難以跟蹤維護和理解