【前端進階之路】沒有入門設計模式?那看這篇就夠了!

一、前言

  • 每一個設計模式我都用一個很簡單的例子來讓你們去理解,入門爲主,深刻爲輔
  • 畢竟深刻設計模式還須要在實際寫代碼、優化代碼的過程當中去深化理解(即須要本身遇到該用的場景)
  • 並不會列舉全部的設計模式,好比java裏的建造者模式,我本身沒有實際場景用過,因此體會不深,咱們就略過。

二、設計模式有哪些

咱們先來看看java裏設計模式有哪些前端

好了,咱們就用這張圖做爲引子,開擼了!!!go! go !go!vue

2.1 設計模式的6大原則

這裏咱們只關心兩大原則(是js經常使用的兩大原則,單一職責原則和開關閉合原則,既然入門,內容就越簡單越好)java

  • S(SRP)單一職責原則

單一職責原則很簡單,一個方法 一個類只負責一個職責,各個職責的程序改動,不影響其它程序。node

// 我的認爲組件化思想就是單一職責原則很好的體現
// 組件的好處在於若是咱們有10個頁面都須要這個組件,那麼咱們就能夠重用這個組件,複用代碼
// 並且當這個組件的邏輯須要改變的時候,通常狀況下咱們只須要改這個組件自己的邏輯就行了,不影響其它組件
複製代碼
  • O(SCP)開放封閉原則

如類、模塊和函數,應當對擴展開放,但對修改關閉mysql

// 舉個例子,我最深的體會就是固定邏輯抽象出來,而後不固定的邏輯方便拓展
// 這裏咱們能夠看到,getUserInfo就是獲取用戶信息的邏輯是不變的,咱們就封裝到一個方法裏,這就是固定邏輯
const getUserInfo = function( callback ){
    $.ajax('http:// xxx.com/getUserInfo', callback)
}

// 可是用獲取的信息作的事情可能不一樣,是變化的,這就是不固定的邏輯
getUserInfo((data)=>{
    console.log(data.userName)
})
getUserInfo((data)=>{
    console.log(data.userId)
})
複製代碼

三、建立型設計模式

在我看來建立型設計模式就是如何建立對象的設計模式,好比咱們常見的建立對象以下react

const obj = new Object()
複製代碼

但實際寫代碼的過程當中,建立對象的複雜度比上面的代碼高不少。接下來介紹一種常見的建立對象的設計模式叫簡單工廠模式webpack

3.一、簡單工廠模式

這個模式我我的以爲核心意義在於只暴露出一個工廠方法,實際上建造什麼樣的實例對象(也就是new 哪個構造函數)咱們不用關心。(我在react15版本源碼裏看到createElement()方法就是用的這種模式)es6

// 暴露出一個工廠類,或者你也寫成構造函數也行,好比說這個工廠類叫User,構造不同的角色,不一樣的角色的有不同的屬性
class User {
  //構造器
  constructor(opt) {
    this.viewPage = opt.viewPage;
    this.name = opt.name;
  }

  //靜態方法,這是內部實現工廠方法的細節
  static getInstance(role) {
    switch (role) {
      case 'superAdmin':
        return new User({ name: '超級管理員', viewPage: ['首頁', '通信錄', '發現頁', '應用數據', '權限管理'] });
        break;
      case 'admin':
        return new User({ name: '管理員', viewPage: ['首頁', '通信錄', '發現頁', '應用數據'] });
        break;
      case 'user':
        return new User({ name: '普通用戶', viewPage: ['首頁', '通信錄', '發現頁'] });
        break;
      default:
        throw new Error('參數錯誤, 可選參數:superAdmin、admin、user')
    }
  }
}

//調用
let superAdmin = User.getInstance('superAdmin');
let admin = User.getInstance('admin');
let normalUser = User.getInstance('user');
複製代碼

3.二、單例模式

 保證一個類僅有一個實例。咱們簡單看一下代碼就明白什麼意思了,單例模式仍是比較好理解。web

// 這是單例類
var Singleton = function( name ){
    this.name = name;
};

// 暴露出一個靜態方法,來獲取單例
Singleton.getInstance = (function(){ 
    var instance = null;
    return function( name ){
        // 第一次執行getInstance函數,由於變量instance是null,因此執行if裏面的語句
        // 第二次執行getInstance函數,由於變量instance是new Singleton( name ),因此執行不執行if裏的語句
        if ( !instance ){
            instance = new Singleton( name );
        }
        return instance;
    }
})();
複製代碼

好啦,建立型設計模式咱們就介紹這兩種,是否是比唱跳rap和籃球要輕鬆一些呢,接下來介紹結構型設計模式ajax

四、結構型設計模式

結構型模式關注於總體最終的結構,經過繼承和組合,構建出更加複雜的結構。就拿下面第一個適配器模式,咱們很快的理解一下什麼叫出更加複雜的結構

4.一、適配器模式

是指將一個接口轉換成本身但願的另一個接口。

// 好比獲取到後端傳來的數據,但這個數據不是咱們想要的格式
// 這個時候就能夠用適配器來轉換一下

function ajaxAdapter(data){
    // 處理數據並返回新數據
    // 經過對象解構,獲取list列表,data數據假如是 {code: 200, data: {list: [{name:1}]} }
    const { data: { list } } = data
    return list
}

$.ajax({
    url: 'http://zxx',
    success(data){
        doSomething(ajaxAdapter(data))
    }
})
複製代碼

4.二、裝飾器模式

是指在不改變原對象的基礎上,經過對其進行包裝拓展(添加屬性或者方法)使原有對象能夠知足用戶更復雜的需求

// 咱們聲明瞭一個對象小蔡,他只會打籃球
let xiaoCai={
  sing(){
    console.log("你們好,我是小蔡,我會打籃球");
  }
};

// 咱們聲明瞭一個對象小李,他也想學小蔡打籃球
let xiaoLi={
  sing(){
    console.log("你們好,我是小李,我也想學打籃球");
  }
};

// 小李在B站看了視頻以後,也會打籃球了 
// 把小李的sing方法保存起來
const xiaoLiSing = xiaoLi.sing
// 重寫小李的sing方法,把小蔡的籃球技術給小李
xiaoLi.sing = () => {
    xiaoLiSing()
    xiaoCai.sing()
}
複製代碼

4.三、外觀模式

爲一組複雜的子系統接口提供一個更高級的統一接口,經過這個接口使得對子系統系統接口的訪問更容易。

// js裏好比根據不一樣瀏覽器作兼容處理,提供統一接口
// 有的瀏覽器經過DOM元素的innerText屬性獲取其中的值,好比<div>hello</div>中hello這個文本
// 有的瀏覽器經過DOM元素的textContent屬性獲取其中的值
function getInnerText(element){
	//判斷element是否支持innerText
	if(typeof element.innerText === 'string'){
		return element.innerText;
	}else{
		return element.textContent;
	}
}
複製代碼

4.四、代理模式

是指

  • 一、代理(proxy)是一個對象,它能夠用來控制對另一個對象的訪問
  • 二、代理對象和本體對象實現了一樣的接口,而且會把任何方法調用傳遞給本體對象
  • 三、簡單的理解就是你須要找一個明星代言你的產品,你撥打的電話通常都是明星經紀人的電話,而不是直接找到明星,這個經紀人其實就是代理,明星是本體
// 舉例場景是緩存代理
// 緩存是指每次傳入的參數和結果緩存到一個對象裏
// 這樣下一次傳入一樣的參數,若是以前在緩存對象裏,就直接拿緩存裏的數據
// 這樣就不用每次都要調用函數去計算,有緩存直接用緩存,提高了效率

var plus = function(...argArray){
    var a = 0;
    for(var i = 0; i < argArray.length; i++){
        a = a + argArray[i]
    }
    return a
}

// 高階函數(將函數做爲參數或者返回值的函數)包裝一下上面的plus函數
var proxyFactory = function(fn) {
    var cache = {}; // 參數緩存列表
    return function(...argArray){
        // 將傳入的參數變爲字符串,做爲
        var argString = argArray.join();
        // 若是在緩存裏就輸出緩存內容
        var argsString = String(argArray)
        if(argsString in cache){
            return cache[argsString]
        }
        // 若是沒有在緩存裏就保存在緩存中
        return cache[argsString] = fn(...argArray)
    }
}
// 測試
const proxyPlus = proxyFactory(plus)
console.log(proxyPlus(5,6,7,8))
複製代碼

4.五、橋接模式

  • 是指在系統沿着多個維度變化的同時,又不增長其複雜度並以達到解耦
  • 個人理解就是把公共邏輯抽象成公用邏輯就是橋接模式
// 這個例子就是抽象層和實現層的解耦
// 提取共同點(抽象層)給每一個對象都添加公共方法,即addMethod方法
 Object.prototype.addMethod = function(name,fn){
     this[name] = fn;
 }
// 建立類並實例化對象(實現層)
 function Box(x,y,z){
    this.x=x;
    this.y=y;
    this.z=z;
}
 
var box=new Box(20,10,10);
// 爲對象拓展方法(橋接方法)

box.addMethod("init",function(){
    console.log("盒子的長度爲:"+this.x+" , 寬度爲:"+this.y+" , 高度爲:"+this.z);
});
// 測試代碼
box.init();
複製代碼

4.六、組合模式

  • 是指在程序設計中,組合模式就是用小的子對象來構建更大的對象,而這些小的子對象自己也是由更小的對象組成的。
  • 這裏只是組合,並無從屬關係。
// 這裏的場景是加入有一堆命令,經過組合模式構建更復雜的,自定義的命令集合
// 宏命令的代碼
var closeDoorCommand = {//做爲葉對象
        execute: function(){
            console.log( '關門' );
        }
    };
var openPcCommand = { //做爲葉對象
    execute: function(){
        console.log( '開電腦' );
    }
};
var openQQCommand = {//做爲葉對象
    execute: function(){
        console.log( '登陸QQ' );
    }
};
//組合模式的根對象
var MacroCommand = function(){
    return {
        commandsList: [],
        add: function( command ){//葉對象做爲數組的元素傳遞到
        //數組中
            this.commandsList.push( command );
        },
        execute: function(){ //執行組合命令
            for ( var i = 0, command; command = this.commandsList[ i++ ]; ){
                command.execute(); //葉對象都有execute方法
            }
        }
    }
};
var macroCommand = MacroCommand();
macroCommand.add( closeDoorCommand );//添加到根對象數組中
macroCommand.add( openPcCommand );//同上
macroCommand.add( openQQCommand );//同上
macroCommand.execute();//執行根命令
複製代碼

4.七、享元模式

  • 運行共享技術有效地支持大量細粒度的對象,避免大量擁有相同內容的小類的開銷(如耗費內存),使你們共享一個類(元類)
// 舉例對象池子
// 對象池是另一種性能優化方案,和享元模式有一些類似之處,但沒有分離內部狀態和外部狀態這個過程
// 創建一個對象池工廠 objectPoolFactory
var objectPoolFactory = function (createObjFn) {
  var objectPool = []; //對象池
  return {
    create: function () { //取出
      var obj = objectPool.length === 0 ? createObjFn.apply(this,arguments) : objectPool.shift();
      return obj;
    },
    recover: function (obj) { //收回
      objectPool.push(obj);
    }
  }
}; 
// 如今利用objectPoolFactory來建立一個裝載一些iframe的對象池
var iframeFactory = objectPoolFactory(function () {
  var iframe = document.createElement('iframe');
  document.body.appendChild(iframe);
  iframe.onload = function () {
    iframe.onload = null; //防止iframe重複加載的bug
    iframeFactory.recover(iframe); //iframe加載完成後往對象池填回節點(收回)
  };
  return iframe;
});
//調用
var iframe1 = iframeFactory.create();
iframe1.src = 'http://www.qq.com'; 
複製代碼

五、行爲型設計模式

建立型設計模式解決了如何建立對象,那建立對象後能作什麼呢,接着結構型模式描述如何將類和對象組合起來,造成更大的結構。

最後就是行爲型設計模式閃亮登場了,行爲型設計模式不只僅關注類和對象的結構,並且重點關注它們之間的相互做用。

5.一、模板方法模式

它定義一個操做中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟,具體實現demo以下

// 模板方法分爲兩部分:一、抽象的父類 二、具體實現的子類
// 父類,咱們這裏舉例稱做飲料類,即Beverage
class Beverage{
    // 衝飲料第一步須要把水煮沸
    boilWater(){
        console.log('把水煮沸')
    }
    // 衝飲料第二部須要把材料放入沸水
    brew(){
        
    }
    // 衝飲料第三步把飲料倒進杯子裏
    addCoundiments(){
        
    }
    init(){
        this.boilWater();
        this.brew();
        this.addCondiments();
    }
}

// 子類,咱們假如沖茶
class Tea extends Beverage{
    // 衝飲料第二步須要把材料放入沸水
    brew(){
        console.log('用沸水泡茶')
    }
    // 衝飲料第三步把飲料倒進杯子裏
    addCondiments(){
        console.log('把茶放入杯子裏')
    }
}
const tea = new Tea()
tea.init()
複製代碼

5.二、命令模式

將請求與實現解耦並封裝成獨立對象,從而使不一樣的請求對客戶端的實現參數化

// 好比二次封裝react的antd組件,我幾乎都是用命令模式去封裝的
// 在個人運用中,這個設計模式就是用數據去驅動UI,好比用的elementUI\antd組件,是否是放入數據,視圖就自動生成了呢
// 這裏簡單將命令模式應用於canvas繪圖
const CanvasCommand = (function(){
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        const Action = {
            // 填充顏色
            fillStyle(c){
                ctx.fillStyle = c
            }
            // 填充矩形
            fillRect(x, y, width, height){
                ctx.fillRect(x, y, width, height)
            }
        }
        return {
            execute(msg){
                if(msg.length){
                        msg.forEach((v)=>{
                            Action[v.command].apply(Action, msg.param)
                        } )
                    } else {
                        return
                    }
                }
            }
        }
    })()
    
    // 寫命令
    CanvasCommand.excute([
        {command: 'fillStyle', param: 'red'},
        {command: 'fillRect', param: [20, 20, 100, 100]}
    ])
複製代碼

5.三、發佈-訂閱者模式

  • 若是要說web前端裏面只有一種設計模式須要學習,那我以爲就是發佈訂閱者模式了
  • 我不說它繁瑣的定義,簡單理解就是,你們用過onClick函數綁定點擊事件吧,當你寫好綁定點擊事件的函數時,這就叫註冊或者訂閱了點擊事件
  • 當你真的點擊你綁定的好比button或者div時,這時叫發佈或者觸發事件,這就是這個設計模式的運用
  • 它的做用好比說redux、vuex是爲了解決模塊之間通訊的,實際上解決的是主體對象和觀察者之間的耦合
// 手寫一個基於發佈訂閱模式的事件綁定機制
const event = {
    // 事件都註冊在eventList數組裏
    eventList: [],
    /**
    * 訂閱事件函數
    * @key {string} 訂閱事件名稱
    * @fn {function} 訂閱事件的回調函數
    **/
    listen(key, fn) {
        if(typeof fn !== 'function'){
            console.log('請添加回調函數');
            return
        }
        if(!this.eventList[key]) {
            this.eventList[key] = []
        }
        this.eventList[key].push(fn)
    },
    trigger(key, ...arg){
        const fns = this.eventList[key];
        if(!fns || fns.length === 0){
            return false
        }
        fns.forEach((fn) => {
            fn.apply(this. arg)
        })
    }
}
// 測試
event.listen('click', function(){ console.log('訂閱事件1') })
event.listen('click', function(){ console.log('訂閱事件2') })
event.trigger('click')
複製代碼

5.四、策略模式

  • 策略模式是定義一系列算法,把它們一個個封裝起來,而且使他們能夠相互替換
  • 舉例以下,假如咱們最近咱們作了一個投票徵集活動,讓網友投票選出咱們本月最美的圖片,每張圖片會有如下幾種結果 一、A等級的圖片會顯示10顆星,B等級的圖片會顯示9顆星,C等級的圖片會顯示8顆星。。。。以此類推F等級圖片顯示5顆星,正常寫法以下第一段代碼,策略模式改進,以下第二段代碼
// 常規寫法
function showResult(level) {
    if(level === 'A'){
        return '10顆星'
    }else if(level === 'B'){
        return '9顆星'
    }else if(level === 'C'){
        return '8顆星'
    }else if(level === 'D'){
        return '7顆星'
    }else if(level === 'E'){
        return '6顆星'
    }else if(level === 'F'){
        return '5顆星'
    }
}
console.log(showResult('F'))
複製代碼

假如if...else有1000種可能,那麼若是這麼判斷下去,要判斷1000次才能匹配if...else的最後一個條件,效率就特別低,如今咱們用策略模式去改進

//  策略模式改進,把if的條件定義爲一個個的算法
const state = {
    A() {
        return '10顆星'
    },
    B() {
        return '9顆星'
    },
    C() {
        return '8顆星'
    },
    D() {
        return '7顆星'
    },
    E() {
        return '6顆星'
    },
    F() {
        return '5顆星'
    },
}
// 策略模式調用
function showResult(level){
    return state[level]()
}
console.log(showResult('F'))
複製代碼

5.五、中介者模式

  • 中介者模式主要解決多個對象和類之間的通訊複雜度,主要經過一箇中介類接受全部消息,而後再進行轉發。
  • 中介者模式的做用就是接觸對象與對象之間的耦合關係(感受有點像發佈訂閱者模式呢。。。。)
// 寫一個JS版的泡泡堂遊戲, 這是本身在書上看到的一個很好的中介者模式案例
// 目前玩家是兩我的,因此當其中一個玩家死亡的時候遊戲便結束,同時通知它的對手勝利
function Player( name ) {
    this.name = name;
    this.enemy = null;
}

Player.prototype.win = function(){
    console.log(this.name + 'won')
}

Player.prototype.lose = function(){
    console.log(this.name + 'lost')
}

Player.prototype.die = function(){
    this.lose();
    this.enemy.win();
}

// 接下來建立2個玩家對象
var player1 = new Player('沈騰');
var player2 = new Player('賈玲');

// 給玩家相互設置敵人
player1.enemy = player2;
player2.enemy = player1;
// 當玩家player1被泡泡炸死的時候,只須要調用這一句代碼 便完成了一局遊戲
player1.die()

// 接下來,假如玩家數量變爲了8個,咱們的遊戲代碼就變成了下面這樣
// 如下建立人物的代碼這裏不須要看懂,接着日後面看就好了,我是怕有些人跑代碼跑不通,因此現把隊伍的人創建起來
// 定一個數組players來保存全部玩家
var players = []
// 紅隊
var player1 = playerFactory('沈騰', 'read');
var player2 = playerFactory('黃海波', 'read');
var player3 = playerFactory('張全蛋', 'read');
var player4 = playerFactory('李大嘴', 'read');

// 藍隊
var player5 = playerFactory('小瀋陽', 'blue');
var player6 = playerFactory('周杰', 'blue');
var player7 = playerFactory('賈乃亮', 'blue');
var player8 = playerFactory('李小璐', 'blue');

// 定義一個工廠來建立玩家
function playerFactory (name, teamColor){
var newPlayer = new Player(name, teamColor); // 建立新玩家
for(var i = 0 ; i < players.length ; i++){ // 通知全部玩家,有新角色加入
    var player =  players[i]
    if(player.teamColor === newPlayer.teamColor){
        player.partners.push(newPlayer);
        newPlayer.partners.push(player);
    }else{
        player.enemies.push(newPlayer); // 相互添加敵人列表
        newPlayer.enemies.push(player);
    }
}
players.push(newPlayer);
return newPlayer;
}
// 接着再改寫構造函數Player, 使每一個玩家對象都增長一些屬性,分別是隊友列表,敵人列表,玩家當前的狀態,角色名字,以及玩家所在隊伍的顏色
function Player(name, teamColor){
    this.partners = []; // 隊友列表
    this.enemies = []; // 敵人的狀態
    this.state = 'live'; // 玩家狀態
    this.name = name; // 角色名字
    this.teamColor = teamColor; // 隊伍顏色
}

// 玩家勝利和失敗以後的展示
Player.prototype.win = function(){
    console.log('winner: ' + this.name) // 玩家團隊勝利
};
Player.prototype.lose = function(){
    console.log('lose: ' + this.name) // 玩家團隊失敗
};

// 玩家死亡以後要遍歷本身的隊友,若是隊友所有死亡則遊戲結束,這局遊戲失敗,同時通知敵人隊伍的全部玩家取得勝利,代碼以下
Player.prototype.die = function(){ // 玩家死亡
    var all_dead = true;
    this.state = 'dead'; // 設置玩家爲死亡狀態
    for(var i = 0, partner; partner = this.partners[i++];){ 
        if(partner.state !== 'dead'){ // 若是還有一個隊友沒有死亡, 則遊戲還未失敗
            all_dead = false;
            break;
        }
    }
    if(all_dead === true){ // 若是隊友所有死亡
        this.lose(); // 通知本身遊戲失敗
        for(var i = 0; i < this.partners.length; i++){ // 通知全部隊友玩家遊戲失敗
            var partner = this.partners[i]
            partner.lose()
        }
        for(var i = 0; i < this.enemies.length; i++ ){ // 通知全部敵人遊戲勝利
             var enemy = this.enemies[i]
            enemy.win()
        }
    }
}

複製代碼

測試代碼以下:

// 讓紅隊玩家所有死亡
player1.die();
player2.die();
player3.die();
player4.die();

複製代碼

咱們能夠看到,每一個玩家都保存裏了隊友列表和對手列表,若是線上有1萬我的,那這個列表就是很是大,每一個人都去遍歷這個列表,開銷太大了,因此咱們須要一種方法來解決這個問題,此時,中介者模式閃亮登場嘍

// 如下的playerDirector就是中介者,咱們最後來實現它,先無論
// 從新定義Player類
function Player(name, teamColor) {
    this.name = name; // 角色名字
    this.teamColor = teamColor; // 隊伍顏色
    this.state = 'alive'; // 玩家生存狀態
};

Player.prototype.win = function () {
    console.log(this.name + ' won ');
};

Player.prototype.lose = function () {
    console.log(this.name + ' lost');
};

// 玩家死亡
Player.prototype.die = function () {
    this.state = 'dead';
    playerDirector.reciveMessage('playerDead', this); // 給中介者發送消息,玩家死亡
};
// 移除玩家
Player.prototype.remove = function () {
    playerDirector.reciveMessage('removePlayer', this); // 給中介者發送消息,移除一個玩家
};
// 玩家換隊
Player.prototype.changeTeam = function (color) {
    playerDirector.reciveMessage('changeTeam', this, color); // 給中介者發送消息,玩家換隊
};

// 建立玩家的工廠函數改成
var playerFactory = function (name, teamColor) {
    var newPlayer = new Player(name, teamColor); // 創造一個新的玩家對象
    playerDirector.reciveMessage('addPlayer', newPlayer); // 給中介者發送消息,新增玩家
    return newPlayer;
};

// 中介者用訂閱發佈這模式實現
var playerDirector = (function () {
    var players = {}, // 保存全部玩家
        operations = {}; // 中介者能夠執行的操做
    // 新增一個玩家
    operations.addPlayer = function (player) {
        var teamColor = player.teamColor; // 玩家的隊伍顏色
        players[teamColor] = players[teamColor] || []; // 若是該顏色的玩家尚未成立隊伍,則
        新成立一個隊伍
        players[teamColor].push(player); // 添加玩家進隊伍
    };
    // 移除一個玩家
    operations.removePlayer = function (player) {
        var teamColor = player.teamColor, // 玩家的隊伍顏色
            teamPlayers = players[teamColor] || []; // 該隊伍全部成員
        for (var i = teamPlayers.length - 1; i >= 0; i--) { // 遍歷刪除
            if (teamPlayers[i] === player) {
                teamPlayers.splice(i, 1);
            }
        }
    };
    // 玩家換隊
    operations.changeTeam = function (player, newTeamColor) { // 玩家換隊
        operations.removePlayer(player); // 從原隊伍中刪除
        player.teamColor = newTeamColor; // 改變隊伍顏色
        operations.addPlayer(player); // 增長到新隊伍中
    };
    operations.playerDead = function (player) { // 玩家死亡
        var teamColor = player.teamColor,
            teamPlayers = players[teamColor]; // 玩家所在隊伍
        var all_dead = true;
        for (var i = 0, player; player = teamPlayers[i++];) {
            if (player.state !== 'dead') {
                all_dead = false;
                break;
            }
        }
        if (all_dead === true) { // 所有死亡
            for (var i = 0, player; player = teamPlayers[i++];) {
                player.lose(); // 本隊全部玩家 lose
            }
            for (var color in players) {
                if (color !== teamColor) {
                    var teamPlayers = players[color]; // 其餘隊伍的玩家
                    for (var i = 0, player; player = teamPlayers[i++];) {
                        player.win(); // 其餘隊伍全部玩家 win
                    }
                }
            }
        }
    };
    var reciveMessage = function () {
        var message = Array.prototype.shift.call(arguments); // arguments 的第一個參數爲消息名稱
        operations[message].apply(this, arguments);
    };
    return {
        reciveMessage: reciveMessage
    }
})(); 

複製代碼

測試代碼

咱們來看下測試結果:
// 紅隊:
var player1 = playerFactory( '皮蛋', 'red' ),
 player2 = playerFactory( '小乖', 'red' ),
 player3 = playerFactory( '寶寶', 'red' ),
 player4 = playerFactory( '小強', 'red' );
// 藍隊:
var player5 = playerFactory( '黑妞', 'blue' ),
 player6 = playerFactory( '蔥頭', 'blue' ),
 player7 = playerFactory( '胖墩', 'blue' ),
 player8 = playerFactory( '海盜', 'blue' );
player1.die();
player2.die();
player3.die();
player4.die(); 
複製代碼

5.六、迭代器模式

  • 這種模式用於順序訪問集合對象的元素,不須要知道集合對象的底層表示。
  • js裏es6的iterator就是迭代器模式就能很好的體現這個設計模式
// 數組生成迭代器須要調用它的Symbol.iterator屬性,就能夠以此遍歷這個數組的每一項了
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
複製代碼

5.七、職責鏈模式

  • 爲了不請求發送者與多個請求處理者耦合在一塊兒,將全部請求的處理者經過前一對象記住其下一個對象的引用而連成一條鏈;當有請求發生時,可將請求沿着這條鏈傳遞,直到有對象處理它爲止。

咱們舉例的場景大概是:公司針對支付過定金的用戶有必定的優惠政策。在正式購買後,已經支付過500元定金的用戶會收到100元的商城優惠券,200元定金的用戶能夠收到50元的優惠券,而以前沒有支付定金的用戶只能進入普通購買模式,也就是沒有商城優惠券,且在庫存有限的狀況下不必定保證能買到。

如下是咱們須要的幾個字段

  • orderType: 表示訂單類型(區分定金用戶和普通購買用戶),code的值是1的時候是500元定金用戶,爲2的時候是200元定金用戶,爲3的時候是普通購買用戶
  • pay:表示用戶是否已經支付定金,值爲true或者false,雖然用戶已經下過500元定金的訂單,但若是他一直沒有支付定金,如今只能降級進入普通購買模式
  • stock:表示當前用戶普通購買的手機庫存數量,已經支付過500元或者200定金的用戶不受此限制
// 實現代碼以下
var order = function(orderType, pay, stock){
    if(orderType === 1){ // 500元定金購買模式
        if(pay === true ){ // 已支付定金
            console.log('500元定金預購,獲得100元優惠券')
        }else{
            if(stock > 0){ // 用於普通購買模式
                console.log('普通購買,無優惠券');
            }else{
                console.log('手機庫存不足')
            }
        }
    }
    else if (orderType === 1){ // 200元定金購買模式
        if(pay === true ){ 
            console.log('200元定金預購,獲得100元優惠券')
        }else{
            if(stock > 0){
                console.log('普通購買,無優惠券');
            }else{
                console.log('手機庫存不足')
            }
        }
    }
    else if (orderType === 3){ // 普通購買模式
        if(stock > 0){
            console.log('普通購買,無優惠券');
        }else{
            console.log('手機庫存不足')
        }
    }
}
order(1, true, 500) // 輸出: 500元定金預購,獲得100元優惠券
複製代碼

這段代碼很繁瑣,難以閱讀,通常狀況下,我會想到用策略模式,把if條件變爲一個個的算法,來減小大量的if...else的判斷

// 500元訂單
var order500 = function(orderType, pay, stock){
    if(orderType === 1 && pay === true){
        console.log('500元定金預購,獲得100元優惠券')
    }else{
        order200(orderType, pay, stock);  // 將請求傳給200元的訂單
    }
}

// 200元訂單
var order200 = function(orderType, pay, stock){
    if(orderType === 2 && pay === true){
        console.log('200元定金預購,獲得50元優惠券')
    }else{
        order200(orderType, pay, stock);  // 將請求傳給普通訂單
    }
}

// 普通訂單
var orderNormal = function(orderType, pay, stock){
    if(stock > 0){
        console.log('普通購買,無優惠券')
    }else{
        console.log('手機庫存不足')
    }
}

// 測試結果
order500(1, true, 500); // 輸出: 500元定金預購,獲得100優惠券
複製代碼

接下來,咱們更深一步,畢竟用責任鏈模式來改造

// 500元訂單
var order500 = function(orderType, pay, stock){
    if(orderType === 1 && pay === true){
        console.log('500元定金預購,獲得100元優惠券')
    }else{
        return 'nextSuccessor'
    }
}

// 200元訂單
var order200 = function(orderType, pay, stock){
    if(orderType === 2 && pay === true){
        console.log('200元定金預購,獲得50元優惠券')
    }else{
        return 'nextSuccessor'
    }
}

// 普通訂單
var orderNormal = function(orderType, pay, stock){
    if(stock > 0){
        console.log('普通購買,無優惠券')
    }else{
        console.log('手機庫存不足')
    }
}
複製代碼

接着咱們寫一個鏈條的類,用來造成咱們的鏈條

var Chain = function( fn ){
    this.fn = fn;
    this.successor = null
}
Chain.prototype.setNextSuccessor = function(successor){ // 這個方法是把請求給鏈條的下一個
    return this.successor = successor
}
Chain.prototype.passRequest = function(...args){ // 傳遞請求給某個節點
    var ret = this.fn(...args);
    if(ret === 'nextSuccessor'){
        return this.successor && this.successor.passRequest(...args)
    }
    return ret
}
複製代碼

如今咱們把3個訂單函數分別包裝成責任鏈的節點:

var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);
複製代碼

而後指定節點在職責鏈中的順序

chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);
複製代碼

最後把請求傳給第一個節點

chainOrder500.passRequest(1, true, 500); // 輸出:500元定金預購,獲得100元優惠券
chainOrder500.passRequest(2, true, 500); // 輸出:200元定金預購,獲得50元優惠券
chainOrder500.passRequest(3, true, 500); // 輸出:普通購買,無優惠券
chainOrder500.passRequest(1, false, 0); // 輸出:手機庫存不足
複製代碼

拓展: 異步責任鏈模式

Chain.prototype.next = function(...args){
    return this.successor && this.successor.passRequest(...args)
}
複製代碼

異步函數以下

var fn1 = new Chain(function(){
    console.log(1);
    return 'nextSuccessor'
});

var fn2 = new Chain(function(){
    console.log(2);
    var self = this;
    setTimeout(function(){
        self.next()
    })
});

var fn3 = new Chain(function(){
    console.log(3);
});
// 測試
fn1.setNextSuccessor(fn2).setNextSuccessor(fn3);
fn1.passRequest()
複製代碼

結語

  • 本文有些案例來自於《JavaScript設計模式與開發實踐》+《JavaScript設計模式》
  • 本身對設計模式的研究目前就告一段落了,更深一步對設計模式的瞭解須要在實戰中不斷學習和思考。
  • 今年的下一個目標就是Node中高級的進階學習 + 搞定基礎算法,因此後面但願能寫一篇前端基礎算法的文章,和一篇node(egg)+mysql(orm框架目前在學習中)+react(dva)+webpack4後臺管理系統項目實戰
  • 路遙遙其修遠兮,還要學的東西太多了。。。。。離2019年還有5個月就結束了,你們加油!!!!
相關文章
相關標籤/搜索