JavaScript設計模式總結

以前看過《JavaScript設計模式與開發實踐》這本書,對書中的設計模式和一些相關案例也有了必定的瞭解,同時把這些設計模式的應用對應在在一些其餘的項目中,進行了一些整理,以下僅供參考:javascript

補充:若是如下內容有什麼不對的地方,歡迎指正。html

設計模式目的

設計模式是爲了更好的代碼重用性,可讀性,可靠性,可維護性。前端

設計六大原則

1)單一職責原則
2)里氏替換原則
3)依賴倒轉原則
4)接口隔離原則
5)最少知識原則(迪米特法則)
6)開放封閉原則
java

設計模式分類

整體來講設計模式分爲三大類:git

建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。github

結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。設計模式

行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。數組

其實還有兩類:併發型模式和線程池模式。瀏覽器

不過,對於前端來講,有的設計模式在平時工做中幾乎用不到或者不多用到,來來來,來了解下前端常見的設計模式併發

JS中的設計模式

常見設計模式:

一、工廠模式

常見的實例化對象模式,工廠模式就至關於建立實例對象的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等

二、單例模式

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點,通常登陸、購物車等都是一個單例。

// 單例對象
    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等

三、適配器模式

用來解決兩個接口不兼容問題,由一個對象來包裝不兼容的對象,好比參數轉換,容許直接訪問

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格式轉換成新的格式等

四、裝飾器模式

在不改變對象自身的基礎上,動態的給某個對象添加新的功能,同時又不改變其接口

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中數組變異方法實現等

五、代理模式

爲其餘對象提供一種代理,便以控制對這個對象的訪問,不能直接訪問目標對象

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訪問、圖片預加載等

六、外觀模式

爲一組複雜的子系統接口提供一個更高級的統一接口,經過這個接口使得對子系統接口的訪問更容易,不符合單一職責原則和開放封閉原則

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事件不一樣瀏覽器兼容處理、同一方法能夠傳入不一樣參數兼容處理等

七、觀察者模式

定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知

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自定義事件,文件流等

八、迭代器模式

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

可分爲:內部迭代器和外部迭代器

內部迭代器: 內部已經定義好迭代規則,外部只須要調用一次便可。

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

九、狀態模式

關鍵是區分事物內部的狀態,事物內部狀態每每會帶來事物的行爲改變,即容許對象在內部狀態發生改變時改變它的行爲

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

應用場景:燈泡狀態、紅綠燈切換等

其餘設計模式:

十、命令模式
十一、組合模式
十二、享元模式
1三、策略模式
1四、職責鏈模式
1五、模板方法模式
1六、中介者模式
1七、備忘錄模式
1八、訪問者模式
1九、解釋器模式
20、橋接模式

其餘設計模式請移步:github.com/jefferyE
更多設計模式,具體請參考:www.runoob.com

相關文章
相關標籤/搜索