之前看過《JavaScript設計模式與開發實踐》這本書,對書中的設計模式和一些相關案例也有了一定的瞭解,同時把這些設計模式的應用對應在在一些其他的專案中,進行了一些整理,如下僅供參考:
補充:如果以下內容有什麼不對的地方,歡迎指正。
設計模式目的
設計模式是為了更好的程式碼重用性,可讀性,可靠性,可維護性。
設計六大原則
1)單一職責原則
2)里氏替換原則
3)依賴倒轉原則
4)介面隔離原則
5)最少知識原則(迪米特法則)
6)開放封閉原則
設計模式分類
總體來說設計模式分為三大類:
建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:介面卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、直譯器模式。
其實還有兩類:併發型模式和執行緒池模式。
不過,對於前端來說,有的設計模式在平時工作中幾乎用不到或者很少用到,來來來,來了解下前端常見的設計模式
JS中的設計模式
常見設計模式:
1、工廠模式
常見的例項化物件模式,工廠模式就相當於建立例項物件的new,提供一個建立物件的介面
// 某個需要建立的具體物件
class Product {
constructor (name) {
this.name = name;
}
init () {}
}
// 工廠物件
class Creator {
create (name) {
return new Product(name);
}
}
const creator = new Creator();
const p = creator.create(); // 通過工廠物件建立出來的具體物件
複製程式碼
應用場景:JQuery中的$、Vue.component非同步元件、React.createElement等
2、單例模式
保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點,一般登入、購物車等都是一個單例。
// 單例物件
class SingleObject {
login () {}
}
// 訪問方法
SingleObject.getInstance = (function () {
let instance;
return function () {
if (!instance) {
instance = new SingleObject();
}
return instance;
}
})()
const obj1 = SingleObject.getInstance();
const obj2 = SingleObject.getInstance();
console.log(obj1 === obj2); // true
複製程式碼
應用場景:JQuery中的$、Vuex中的Store、Redux中的Store等
3、介面卡模式
用來解決兩個介面不相容問題,由一個物件來包裝不相容的物件,比如引數轉換,允許直接訪問
class Adapter {
specificRequest () {
return '德國標準插頭';
}
}
// 介面卡物件,對原來不相容物件進行包裝處理
class Target {
constructor () {
this.adapter = new Adapter();
}
request () {
const info = this.adapter.specificRequest();
console.log(`${info} - 轉換器 - 中國標準插頭`)
}
}
const target = new Target();
console.log(target.request()); // 德國標準插頭 - 轉換器 - 中國標準插頭
複製程式碼
應用場景:Vue的computed、舊的JSON格式轉換成新的格式等
4、裝飾器模式
在不改變物件自身的基礎上,動態的給某個物件新增新的功能,同時又不改變其介面
class Plane {
fire () {
console.log('傳送普通子彈');
}
}
// 裝飾過的物件
class Missile {
constructor (plane) {
this.plane = plane;
}
fire () {
this.plane.fire();
console.log('發射導彈');
}
}
let plane = new Plane();
plane = new Missile(plane);
console.log(plane.fire()); // 依次列印 傳送普通子彈 發射導彈
複製程式碼
利用AOP給函式動態新增功能,即Function的after或者before
Function.prototype.before = function (beforeFn) {
const _self = this;
return function () {
beforeFn.apply(this, arguments);
return _self.apply(this, arguments);
}
}
Function.prototype.after = function (afterFn) {
const _self = this;
return function () {
const ret = _self.apply(this, arguments);
afterFn.apply(this, arguments);
return ret;
}
}
let func = function () {
console.log('2');
}
func = func.before(function() {
console.log('1');
}).after(function() {
console.log('3');
})
func();
console.log(func()); // 依次列印 1 2 3
複製程式碼
應用場景:ES7裝飾器、Vuex中1.0版本混入Vue時,重寫init方法、Vue中陣列變異方法實現等
5、代理模式
為其他物件提供一種代理,便以控制對這個物件的訪問,不能直接訪問目標物件
class Flower {}
// 源物件
class Jack {
constructor (target) {
this.target = target;
}
sendFlower (target) {
const flower = new Flower();
this.target.receiveFlower(flower)
}
}
// 目標物件
class Rose {
receiveFlower (flower) {
console.log('收到花: ' + flower)
}
}
// 代理物件
class ProxyObj {
constructor () {
this.target = new Rose();
}
receiveFlower (flower) {
this.sendFlower(flower)
}
sendFlower (flower) {
this.target.receiveFlower(flower)
}
}
const proxyObj = new ProxyObj();
const jack = new Jack(proxyObj);
jack.sendFlower(proxyObj); // 收到花:[object Object]
複製程式碼
應用場景:ES6 Proxy、Vuex中對於getters訪問、圖片預載入等
6、外觀模式
為一組複雜的子系統介面提供一個更高階的統一介面,通過這個介面使得對子系統介面的訪問更容易,不符合單一職責原則和開放封閉原則
class A {
eat () {}
}
class B {
eat () {}
}
class C {
eat () {
const a = new A();
const b = new B();
a.eat();
b.eat();
}
}
// 跨瀏覽器事件偵聽器
function addEvent(el, type, fn) {
if (window.addEventListener) {
el.addEventListener(type, fn, false);
} else if (window.attachEvent) {
el.attachEvent('on' + type, fn);
} else {
el['on' + type] = fn;
}
}
複製程式碼
應用場景:JS事件不同瀏覽器相容處理、同一方法可以傳入不同引數相容處理等
7、觀察者模式
定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知
class Subject {
constructor () {
this.state = 0;
this.observers = [];
}
getState () {
return this.state;
}
setState (state) {
this.state = state;
this.notify();
}
notify () {
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 sub = new Subject();
let observer1 = new Observer('o1', sub);
let observer2 = new Observer('o2', sub);
sub.setState(1);
複製程式碼
觀察者模式與釋出/訂閱模式區別: 本質上的區別是排程的地方不同
雖然兩種模式都存在訂閱者和釋出者(具體觀察者可認為是訂閱者、具體目標可認為是釋出者),但是觀察者模式是由具體目標排程的,而釋出/訂閱模式是統一由排程中心調的,所以觀察者模式的訂閱者與釋出者之間是存在依賴的,而釋出/訂閱模式則不會。
---觀察者模式:目標和觀察者是基類,目標提供維護觀察者的一系列方法,觀察者提供更新介面。具體觀察者和具體目標繼承各自的基類,然後具體觀察者把自己註冊到具體目標裡,在具體目標發生變化時候,排程觀察者的更新方法。
比如有個“天氣中心”的具體目標A,專門監聽天氣變化,而有個顯示天氣的介面的觀察者B,B就把自己註冊到A裡,當A觸發天氣變化,就排程B的更新方法,並帶上自己的上下文。
---釋出/訂閱模式:訂閱者把自己想訂閱的事件註冊到排程中心,當該事件觸發時候,釋出者釋出該事件到排程中心(順帶上下文),由排程中心統一排程訂閱者註冊到排程中心的處理程式碼。
比如有個介面是實時顯示天氣,它就訂閱天氣事件(註冊到排程中心,包括處理程式),當天氣變化時(定時獲取資料),就作為釋出者釋出天氣資訊到排程中心,排程中心就排程訂閱者的天氣處理程式。
應用場景:JS事件、JS Promise、JQuery.$CallBack、Vue watch、NodeJS自定義事件,檔案流等
8、迭代器模式
提供一種方法順序訪問一個聚合物件中各個元素, 而又無須暴露該物件的內部表示
可分為:內部迭代器和外部迭代器
內部迭代器: 內部已經定義好迭代規則,外部只需要呼叫一次即可。
const each = (args, fn) => {
for (let i = 0, len = args.length; i < len; i++) {
const value = fn(args[i], i, args);
if (value === false) break;
}
}
複製程式碼
應用場景: JQuery.each方法
外部迭代器:必須顯示的請求迭代下一個元素。
// 迭代器
class Iterator {
constructor (list) {
this.list = 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;
}
}
const arr = [1, 2, 3, 4, 5, 6];
const ite = new Iterator();
while(ite.hasNext()) {
console.log(ite.next()); // 依次列印 1 2 3 4 5 6
}
複製程式碼
應用場景:JS Iterator、JS Generator
9、狀態模式
關鍵是區分事物內部的狀態,事物內部狀態往往會帶來事物的行為改變,即允許物件在內部狀態發生改變時改變它的行為
// 紅燈
class RedLight {
constructor (state) {
this.state = state;
}
light () {
console.log('turn to red light');
this.state.setState(this.state.greenLight)
}
}
// 綠燈
class greenLight {
constructor (state) {
this.state = state;
}
light () {
console.log('turn to green light');
this.state.setState(this.state.yellowLight)
}
}
// 黃燈
class yellowLight {
constructor (state) {
this.state = state;
}
light () {
console.log('turn to yellow light');
this.state.setState(this.state.redLight)
}
}
class State {
constructor () {
this.redLight = new RedLight(this)
this.greenLight = new greenLight(this)
this.yellowLight = new yellowLight(this)
this.setState(this.redLight) // 初始化為紅燈
}
setState (state) {
this.currState = state;
}
}
const state = new State();
state.currState.light() // turn to red light
setInterval(() => {
state.currState.light() // 每隔3秒依次列印紅燈、綠燈、黃燈
}, 3000)
複製程式碼
應用場景:燈泡狀態、紅綠燈切換等
其他設計模式:
10、命令模式
11、組合模式
12、享元模式
13、策略模式
14、職責鏈模式
15、模板方法模式
16、中介者模式
17、備忘錄模式
18、訪問者模式
19、直譯器模式
20、橋接模式
其他設計模式請移步:github.com/jefferyE
更多設計模式,具體請參考:www.runoob.com