Javascript設計模式

Javascript設計模式記錄,這個方面確實是沒寫過,工做中也沒有用到js設計模式的地方。javascript

prototype與面向對象取捨

使用prototype原型繼承和使用面向對象,均可以實現閉包的效果。那麼這兩個的選擇點,就是方法會不會產生多個實例java

例如,咱們須要作一個閉包數組,並給他提供一個添加方法。算法

 1 !(function () {
 2     //原型繼承寫法
 3     var Validator = function(){
 4         this.cache = [];
 5     };
 6     Validator.prototype.add = function(item){
 7         this.cache.push(item);
 8     };
 9     var validator = new Validator(),validatorr = new Validator();
10     validator.add("test1"); console.log(validator.cache);
11     validatorr.add("test2"); console.log(validatorr.cache);
12     //面向對象寫法
13     var Validator2 = {
14         cache : [],
15         add : function(item){
16             this.cache.push(item);
17         }
18     };
19     Validator2.add("test3"); console.log(Validator2.cache);
20     Validator2.add("test4"); console.log(Validator2.cache);
21 })()
prototype

這兩種寫法均可以實現閉包,可是面向對象的寫法,只能存在一個。咱們沒法對他進行初始化,而原型繼承寫法,咱們則能夠對他進行初始化操做。設計模式

因此當,咱們認爲這個方法,在整個程序中,是惟一的存在。咱們可使用面向對象的寫法,若是能夠存在多個,則使用prototype這種寫法。數組

調用父類構造函數

繼承關係的兩個對象,在實例的過程當中,能夠經過修改指向,來調整調用構造函數。緩存

!(function () {
    var A = function (light) {
        this.light1 = light;
    };
    var B = function (light) {
        this.light = light;
        A.apply(this,arguments);//你須要手動調用A的構造方法
    };
    //給B賦值的同時,給A賦值
    B.prototype = new A();
    var C = new B(123);
    console.log(C.light);
    console.log(C.light1);
})()
構造函數

單例模式

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。例如:線程池,全局緩存,登陸浮窗。閉包

首先咱們須要把單例的邏輯代碼單獨提取,而後使用惰性單例的方式,也就是返回方法。只有在點擊的時候,纔會進行執行。app

javascript的單例,跟類不同。無需建立多餘的構造函數這些,直接建立全局變量便可。框架

 1 !(function () {
 2     //管理單例的邏輯代碼,若是沒有數據則建立,有數據則返回
 3    var getSingle = function(fn){ //參數爲建立對象的方法
 4        var result;
 5        return function(){ //判斷是Null或賦值
 6            return result || (result = fn.apply(this,arguments));
 7        };
 8    };
 9     //建立登陸窗口方法
10     var createLoginLayer = function(){
11         var div = document.createElement('div');
12         div.innerHTML = '我是登陸浮窗';
13         div.style.display = 'none';
14         document.body.appendChild(div);
15         return div;
16     };
17     //單例方法
18     var createSingleLoginLayer = getSingle(createLoginLayer);
19 
20     //使用惰性單例,進行建立
21     document.getElementById('loginBtn').onclick = function(){
22         var loginLayer = createSingleLoginLayer();
23         loginLayer.style.display = 'block';
24     };
25 })()
單例模式

策略模式

定義一系列的算法,把它們一個一個封裝起來。將算法的使用與算法的實現分離開來。dom

javascript的策略模式很簡單,把算法直接定義成函數便可。

 1 !(function () {
 2     //定義算法方法
 3     var strategies = {
 4         "S":function(salary){
 5           return salary * 4;
 6         },
 7         "A":function(salary){
 8             return salary * 3;
 9         },
10         "B":function(salary){
11             return salary * 2;
12         }
13     };
14     //執行算法
15     var calculateBouns = function(level,salary){
16       return strategies[level](salary);
17     };
18     console.log(calculateBouns('S',2000));
19 })() 
策略模式

undefined終止循環

(寫具體代碼以前,先記錄一個知識點)。當循環表達式爲undefined時,循環會終止。

!(function(){
    var cale = [1,2,3];
    for(var i= 0,validate;validate=cale[i];)
    {
        cale.shift();
        console.log(validate);
    }
})() //1,2,3

登陸驗證表單

下面寫一個使用策略模式,製做的驗證表單登陸效果。

傳統的表單登陸效果,會在提交後進行一系列的判斷驗證。這樣提交方法很龐大,並且缺乏彈性,複用性也不好。

咱們可使用策略模式,來避免這些問題。

<form action="post" id="registerForm">
    <input type="text" name="userName" />
    <input type="text" name="password" />
    <input type="text" name="phoneNumber" />
    <button>提交</button>
</form>
頁面
!(function () {
    //定義驗證規則,使用策略模式,直接經過 strategies[isNonEmpty]()能夠訪問
    var strategies = {
        isNonEmpty: function (value, errorMsg) { //不爲空
            if (value === "") {
                return errorMsg;
            }
        },
        minLength: function (value, length, errorMsg) { //最小長度
            if (value.length < length) {
                return errorMsg;
            }
        },
        isMobile: function (value, errorMsg) { //手機號碼格式
            if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
                return errorMsg;
            }
        }
    };
    //建立驗證邏輯,使用閉包定義全局數組,存放驗證方法
    var Validator = function () {
        this.cache = [];
    };
    //添加驗證邏輯方法,參數:元素,驗證名稱,錯誤信息
    Validator.prototype.add = function (dom, rules) {
        var self = this;
        for (var i = 0, rule; rule = rules[i++];) {
            (function (rule) {
                var ary = rule.strategy.split(":"); //限制最小數值,進行分割。如沒有:號,則直接返回
                var errorMsg = rule.errorMsg;
                self.cache.push(function () { //將操做方法封裝到全局數組中
                    var strategy = ary.shift(); //獲取驗證方法名稱,並刪除
                    ary.unshift(dom.value); //往開頭添加待驗證元素
                    ary.push(errorMsg); //添加驗證失敗錯誤信息
                    return strategies[strategy].apply(dom, ary); //傳遞數組給方法,由於不涉及this,dom也可傳遞null
                });
            })(rule)
        }
    };
    //添加啓動方法
    Validator.prototype.start = function () {
        //將數組中的方法,分別執行。數組undefined,則跳出循環
        for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
            var msg = validatorFunc();
            //又失敗就進行跳出
            if (msg) {
                return msg;
            }
        }
    };
    //處理校驗
    var validatorFunc = function () {
        var validator = new Validator(); //建立驗證邏輯\
        //添加驗證條件
        validator.add(registerForm.userName, [
            {
                strategy: 'isNonEmpty',
                errorMsg: '用戶不能爲空'
            }, {
                strategy: 'minLength:2',
                errorMsg: '用戶不能少於2位'
            }]);
        validator.add(registerForm.password, [
            {
                strategy: 'minLength:6',
                errorMsg: '密碼長度不能少於6位'
            }]);
        validator.add(registerForm.phoneNumber, [
            {
                strategy: 'isMobile',
                errorMsg: '手機號碼格式不正確'
            }]);
        var errorMsg = validator.start();
        return errorMsg;
    };
    var registerForm = document.getElementById("registerForm");
    registerForm.onsubmit = function () {
        var errorMsg = validatorFunc();
        if (errorMsg) { //判斷是否有這個參數
            alert(errorMsg);
            return false;
        }
    };
})()
javascript

果真,有邏輯的js,很是好玩。比CSS好玩多了。感受有不少委託、多態的思想。

策略模式的優勢與缺點

  1. 有效的避免許多重複的複製粘貼做業。
  2. 開閉原則的完美支持,算法徹底獨立易於切換、理解、拓展。
  3. 算法複用性強
  4. 使用組合和委託讓Validator類擁有執行算法的能力,也是繼承的一種輕便替代方式

缺點

  1. 會增長許多策略類或策略對象。
  2. 違反迪米特法則,會將strategy暴露給客戶全部實現。

代理模式

爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。當客戶不方便直接訪問一個對象的時候,須要提供一個替身對象來控制對這個對象的訪問。

代理模式分爲:虛擬代理和保護代理

虛擬代理:把一些開銷很大的對象,延遲到真正須要它的時候纔去建立。

保護代理:用於控制不一樣權限的對象對目標對象的訪問。

圖片預加載

使用虛擬代理能夠完成圖片預加載功能,先用一張loading圖片佔位,而後用異步方式加載圖片,等圖片加載完畢後填充到img節點裏。

由於javascript事件,均爲異步事件。因此當執行proxyImage時,會先設置loading.gif,等圖片加載完畢後,會執行myImage操做。

 var myImage = (function(){
       var imgNode = document.createElement('img');
       document.body.appendChild(imgNode);
       return {
           setSrc:function(src){
               imgNode.src = src;
           }
       };
   })();
//預加載方法
    var proxyImage = (function(){
        var img = new Image();
        img.onload = function(){
            myImage.setSrc(this.src);
        }
        return {
            setSrc:function(src){
                myImage.setSrc("loading.gif");
                img.src = src;
            }
        };
    })();
    proxyImage.setSrc('實際圖片.jpg'); //預加載
    myImage.setSrc('實際圖片'.jpg); //普通加載
圖片異步加載

注意:加載方法和預加載方法,必須使用當即執行函數,否則setSrc方法調用不到。

如上預加載功能,之因此使用代理模式,主要是爲了不違反,單一職責設計原則。

如不使用代理模式,會執行加載圖片和預加載操做。當咱們不須要預加載功能的時候,沒法進行快速隔離。

虛擬代理中的惰性加載

將虛擬代理運用到惰性加載中,可讓真實的代碼延遲到真正實用的時候才進行添加。

var miniConsole = (function () {
        var cache = [];
        var handler = function (ev) { //監聽按鍵事件
            if (ev.keyCode === 113) {
                var script = document.createElement('script');
                script.onload = function () {
                    for (var i = 0, fn; fn = cache[i++];) {
                        fn();
                    }
                };
                script.src = 'minConsole.js';
                document.getElementsByTagName('head')[0].appendChild(script);
                document.body.removeEventListener('keydown', handler); //只加載一次
            }
        };
        document.body.addEventListener('keydown', handler, false);
        return {
            log: function () {
                var args = arguments;
                cache.push(function () {
                    return miniConsole.log.apply(miniConsole, args);
                });
            }
        };
    })();
    miniConsole.log(11);
    miniConsole = {
        log: function () {
            console.log(Array.prototype.join.call(arguments));
        }
    };
惰性加載

緩存代理

緩存代理能夠爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,可使用以前的計算結果。

例如使用緩存代理計算乘積

var mult = function () {
        var a = 1;
        for (var i = 0, l = arguments.length; i < l; i++) {
            a = a * arguments[i];
        }
        return a;
    }
    //不使用緩存mult(2,3);
    var proxyMult = (function () {
        var cache = {};
        return function () {
            var args = Array.prototype.join.call(arguments, ','); //把參數放在一個字符串裏
            if (args in cache) {
                return cache[args];
            }
            return cache[args] = mult.apply(this,arguments);
        };
    })();
    //使用緩存proxyMult(2,3)
緩存代理

代理工廠 

傳入高階函數能夠爲各類計算方法建立緩存代理。將計算方法傳入專門用於建立緩存代理的工廠中,這樣就能夠建立緩存代理了。

這是使用策略模式,進行建立的一種寫法。能夠直接定義方法便可。

var strate = {
        mult: function () {
            var a = 1;
            for (var i = 0, l = arguments.length; i < l; i++) {
                a = a * arguments[i];
            }
            return a;
        },
        plus: function () {
            var a = 0;
            for (var i = 0, l = arguments.length; i < l; i++) {
                a = a + arguments[i];
            }
            return a;
        }
    };
    var createProxyFactory = function (fn) {
        var cache = {};
        return function () {
            var args = Array.prototype.join.call(arguments, ',');
            if (args in cache) {
                return cache[args];
            }
            return cache[args] = fn.apply(this, arguments);
        };
    };
    var proxyMult = createProxyFactory(strate["mult"]);
    console.log(proxyMult(1,2,3,4));
代理工廠

觀察者模式

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

例如事件綁定,就是一個標準的觀察者模式。

document.body.addEventListener('click',function(){
       console.log(2);
    },false);
    document.body.click();

下面進行一個實際的例子,售樓處能夠接受買房登記,登記後的用戶若是有房源,則會逐一告知。 而且能夠進行取消登記操做。

爲了完全結束耦合性,可使用全局變量製做監聽事件。

 var ObserverEvent = (function () {
        var clientList = [], listen, trigger, remove;
        listen = function (key, fn) {
            if (!clientList[key]) {
                clientList[key] = [];
            }
            clientList[key].push(fn);
        };
        trigger = function () {
            var key = Array.prototype.shift.call(arguments), fns = clientList[key];
            if (!fns || fns.length === 0) {
                return false;
            }
            for (var i = 0, fn; fn = fns[i++];) {
                fn.apply(this, arguments);
            }
        };
        remove = function (key, fn) {
            var fns = clientList[key];
            if (!fns) {
                return false;
            }
            if (!fn) {
                fns && (fns.length = 0);
            } else {
                for (var l = fns.length - 1; l >= 0; l--) {
                    var _fn = fns[l];
                    if (_fn === fn) {
                        fns.splice(l, 1);
                    }
                }
            }
        };
        return {
            listen:listen,
            trigger:trigger,
            remove:remove
        }
    })();
    ObserverEvent.listen('squareMeter88', fn1 = function (price) {
        console.log('價格=' + price);
    });
    ObserverEvent.listen('squareMeter100', function (price) {
        console.log('價格=' + price);
    });
    ObserverEvent.trigger('squareMeter88', 200000);
    ObserverEvent.trigger('squareMeter100', 300000);
    ObserverEvent.remove('squareMeter88', fn1);
    ObserverEvent.trigger('squareMeter88', 200000);
售樓處例子

固然這種售樓處只是一個例子,在現實中,登陸頁面登陸後,會須要刷新各個模塊的信息(頭像、nav)這類。咱們也可使用觀察者模式進行刷新操做。

咱們直接改用調用方法便可,並且是徹底的解耦合

 var header = (function () {
        ObserverEvent.listen('loginSucc', function (data) {
            header.setAvatar(data.avatar);
        });
        return {
            setAvatar: function (data) {
                console.log(data + "設置header成功");
            }
        }
    })();
    var nav = (function () {
        ObserverEvent.listen('loginSucc', function (data) {
            nav.setAvatar(data.avatar)
        });
        return {
            setAvatar: function (data) {
                console.log(data + '設置nav成功');
            }
        }
    })();
    var data = {};
    data.avatar = "參數";
    ObserverEvent.trigger('loginSucc', data);
刷新模塊信息

觀察者模式的優勢很明顯:時間上的解耦,對象之間的解耦。

命令模式

命令模式指的是一個執行某些特定事情的指令。常見的應用場景是:有時候須要向對象發送請求,但不知道接受者是誰,也不知道請求的操做是什麼。

例如:訂餐,客人須要給廚師發送請求,至於那個廚師作,作的步驟。客人不知道。這就是命令模式。

命令模式的一個簡單例子

//定義命令模式執行
    var setCommand = function (button, func) {
        button.onclick = function () {
            func.execute();
        };
    };
    var MenuBar = {
        refresh: function () {
            console.log("刷新頁面");
        }
    };
    var RefreshMenuBarCommand = function (receiver) {
        return {
            execute: function () {
                receiver.refresh();
            }
        }
    };
    var refreshMenuBarCommand = RefreshMenuBarCommand("MenuBar");
    setCommand(button1,refreshMenuBarCommand);
命令模式

命令模式的用處很大,也能夠作撤銷命令,回放這種功能。好比,咱們把用戶按鍵命令作一個封裝,製做一個播放功能。

如下代碼能夠執行並記錄按鍵,當點擊按鈕時,會執行按鍵對應動做。

//定義按鍵動做
    var Ryu = {
        W: function () {
            console.log("用戶按下W");
        },
        S: function () {
            console.log("用戶按下S");
        }
    };
    //建立命令
    var makeCommand = function (receiver, state) {
        return function () {
            if(receiver[state])
                receiver[state]();
        }
    };
    //可執行按鍵Json
    var commands = {
        "119": "W",
        "115": "S"
    };
    //保存按鍵記錄
    var commandStack = [];
    document.onkeypress = function (ev) {
        var keyCode = ev.keyCode, command = makeCommand(Ryu, commands[keyCode]);
        if (command) {
            command();
            commandStack.push(command);
        }
    };
    //註冊按鍵監聽
    document.getElementById("replay").addEventListener('click', function () {
        var commad;
        while (command = commandStack.shift()) {
            command();
        }
    }, false);
按鍵監聽

宏命令

宏命令能夠一次執行一組命令。咱們定義了各類指令,定義瞭如何執行指令。就能夠作成一組命令的這種模式了。

針對不一樣的步驟,也能夠只用此方法。例如: 

var macroCommand2 = new MacroCommand();
macroCommand2.add(macroCommand);
macroCommand2.execute();
能夠指定不一樣命令。
!(function () {
    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();
              }
          }
      };
    };
    var macroCommand = new MacroCommand();
    macroCommand.add(closeDoorCommand);
    macroCommand.add(openPcCommand);
    macroCommand.execute();
})()
宏命令

模板方法模式

模板方法是一種只須要繼承就能夠實現的很是簡單的模式。封裝了子類的算法框架,包含一些公共方法一級封裝子類中的全部方法執行順序。

咖啡與茶。咖啡的步驟:1.燒水,2.沖泡,3.倒進杯子,4.放牛奶。茶葉的步驟:1.燒水,2.浸泡,3.倒進杯子,4.加檸檬

咱們分離不一樣點:2,4。而後使用抽象父類定義,並實現對應方法。其中的init方法,就是模板方法,由於他封裝了子類算法框架就,做爲一個算法的模板。

!(function () {
   var Beverage = function(){};
    Beverage.prototype.boilWater = function(){ //燒水
        console.log("把水煮沸");
    };
    Beverage.prototype.brew = function(){}; //第二步,方法
    Beverage.prototype.pourInCup = function(){};
    Beverage.prototype.addCondiments=function(){};
    Beverage.prototype.init = function(){
        this.boilWater();
        this.brew();
        this.pourInCup();
        this.addCondiments();
    };
    //建立咖啡子類
    var Coffee = function(){};
    Coffee.prototype = new Beverage();
    Coffee.prototype.brew = function(){
        console.log("用沸水沖泡咖啡");
    };
    Coffee.prototype.pourInCup = function(){
        console.log("把咖啡倒進杯子");
    };
    Coffee.prototype.addCondiments = function(){
        console.log("加糖加牛奶");
    };
    var Coffee = new Coffee();
    Coffee.init();
})()
模板命令

針對模板方法,有一些個性的子類,不打算接受模板約束。那麼可使用鉤子方法來建立。

咱們修改模板方法讓他適應鉤子方法。

Beverage.prototype.customerWantsCondiments = function () {
        return true;
    };
    Beverage.prototype.init = function () {
        this.boilWater();
        this.brew();
        this.pourInCup();
        if (this.customerWantsCondiments()) {
            this.addCondiments();
        }
    };
    //建立咖啡子類
    var Coffee = function () {
    };
    Coffee.prototype = new Beverage();
    Coffee.prototype.customerWantsCondiments = function(){
        return window.confirm("請問須要調料嗎");
    }
鉤子方法

享元模式 

享元模式的核心是運用共享技術來有效支持大量細粒度的對象。

例如,如今有50件男裝和50件女裝,分別須要模特穿上而且拍照。若是咱們不使用享元模式,那麼就須要new 100個模特。

使用享元模式,只須要new 2個模特,而後讓他們穿上不一樣的衣服便可。

!(function () {
    var Model = function (sex) {
        this.sex = sex;
    };
    Model.prototype.takePhoto = function () {
        console.log("sex=" + this.sex + " underwear=" + this.underwear);
    };
    var maleModel = new Model("male");
    var femaleModel = new Model("female");
    for(var i=1;i<=50;i++){
        maleModel.underwear = "underwear"+i;
        maleModel.takePhoto();
    }
})()
享元模式

享元模式的使用取決於:一個程序中使用了大量類似對象。形成很大的內存開銷。大多數狀態是外部狀態。能夠用較少的功效對象取代大量對象。

對象池 

對象池維護一個裝載空閒對象的池子,若是須要對象的時候,不是直接new,而是轉從對象池裏獲取。若是沒有空閒對象則建立,完成職責後再次進入池子。

咱們作一個公用的對象池,來維護新建dom對象。

!(function () {
    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);
            }
        };
    };
    var iframeFactory = objectPoolFactory(function () {
        var iframe = document.createElement("iframe");
        document.body.appendChild(iframe);
        iframe.onload = function () {
            iframe.onload = null;
            iframeFactory.recover(iframe);
        }
        return iframe
    });
    var iframe1 = iframeFactory.create();
    iframe1.src = "http://www.baidu.com";

    setTimeout(function(){
        var iframe2 = iframeFactory.create();
        iframe2.src = "http://www.baidu.com";
    },2000);
})()
對象池

職責鏈模式

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

使用orderType和pay來控制流向。分別進行不一樣對象的流轉。

!(function () {
    var order500 = function (orderType, pay, stock) {
        if (orderType === 1 && pay === true) {
            console.log("500元定金");
        } else {
            order200(orderType, pay, stock);
        }
    };
    var order200 = function (orderType, pay, stock) {
        if (orderType === 2 && pay === true) {
            console.log("200元定金");
        } else {
            orderNormal(orderType, pay, stock);
        }
    };
    var orderNormal = function (orderType, pay, stock) {
        if (stock > 0) {
            console.log("普通購買");
        } else {
            console.log("手機庫存不足");
        }
    };
    order500(1,true,500);
})()
職責鏈模式一

可是這種責任鏈體系,耦合度比較高,例如500對象與200對象,耦合度很高。

!(function () {
    var order500 = function (orderType, pay, stock) {
        if (orderType === 1 && pay === true) {
            console.log("500元定金");
        } else {
            return "nextSuccessor";
        }
    };
    var order200 = function (orderType, pay, stock) {
        if (orderType === 2 && pay === true) {
            console.log("200元定金");
        } 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.success = null;
    };
    Chain.prototype.setNextSuccessor = function(successor){
        return this.success = successor;
    };
    Chain.prototype.passRequest = function(){
        var ret = this.fn.apply(this,arguments);
        if(ret === "nextSuccessor"){
            return this.success && this.success.passRequest.apply(this.success,arguments);
        }
    };
    var chainOrder500 = new Chain(order500);
    var chainOrder200 = new Chain(order200);
    chainOrder500.setNextSuccessor(chainOrder200);
    chainOrder500.passRequest(2,true,200);
})()
職責鏈模式二

中介者模式

中介者模式的做用是解除對象與對象之間的緊耦合關係。增長中介者後,全部的相關對象都經過中介者對象來通訊。

泡泡堂例子

 1 !(function () {
 2     var playerDirector = (function () {
 3         var players = {}, operations = {};
 4         operations.addPlayer = function (player) {
 5             var teamColor = player.teamColor;
 6             players[teamColor] = players[teamColor] || [];
 7             players[teamColor].push(player);
 8         };
 9         operations.removePlayer = function (player) {
10             var teamColor = player.teamColor, teamPlayers = players[teamColor] || [];
11             for (var i = teamPlayers.length - 1; i >= 0; i++) {
12                 if (teamPlayers[i] === player) {
13                     teamPlayers.splice(i, 1);
14                 }
15             }
16         };
17         operations.changeTeam = function (player, newTeamColor) {
18             operations.removePlayer(player);
19             player.teamColor = newTeamColor;
20             operations.addPlayer(player);
21         };
22         operations.playerDead = function (player) {
23             var teamColor = player.teamColor, teamPlays = players[teamColor];
24             var all_dead = true;
25             for (var i = 0, player; player = teamPlays[i++];) {
26                 if (player.state !== "dead") {
27                     all_dead = false;
28                     break;
29                 }
30             }
31             if (all_dead === true) {
32                 for (var i = 0, player; player = teamPlays[i++];) {
33                     player.lose();
34                 }
35                 for (var color in players) {
36                     if (color != teamColor) {
37                         var teamPlayers = players[color];
38                         for (var i = 0, player; player = teamPlayers[i++];) {
39                             player.win();
40                         }
41                     }
42                 }
43             }
44         };
45         var ReceiveMessage = function () {
46             var message = Array.prototype.shift.call(arguments);
47             operations[message].apply(this, arguments);
48         };
49         return {
50             ReceiveMessage:ReceiveMessage
51         };
52     })();
53     function Player(name, teamColor) {
54         this.name = name;
55         this.teamColor = teamColor;
56         this.state = "alive";
57     }
58     Player.prototype.win = function () {
59         console.log(this.name + " win ");
60     };
61     Player.prototype.lose = function () {
62         console.log(this.name + " lose ");
63     }
64     Player.prototype.die = function () {
65         this.state = "dead";
66         playerDirector.ReceiveMessage("playerDead", this);
67     };
68     Player.prototype.remove = function () {
69         playerDirector.ReceiveMessage("removePlayer", this);
70     };
71     Player.prototype.changeTeam = function (color) {
72         playerDirector.ReceiveMessage("changeTeam", this, color);
73     };
74     var PlayerFacotry = function (name, teamColor) {
75         var newPlayer = new Player(name, teamColor);
76         playerDirector.ReceiveMessage("addPlayer", newPlayer);
77         return newPlayer;
78     };
79 
80     //測試
81     var player1 = PlayerFacotry("皮蛋","red"),
82         player2 = PlayerFacotry("小乖","red");
83     var player3 = PlayerFacotry("黑妞","blue"),
84         player4 = PlayerFacotry("蔥頭","blue");
85     player1.die();player2.die();
86 })()
泡泡堂例子

中介者模式是迎合迪米特法則的一種實現。指一個對象應該儘量的少了解另外的對象。若是對象之間的耦合性過高,一個對象改變後,會影響其餘對象。

裝飾者模式

裝飾者模式能夠動態的給某個對象添加一些額外的職責,而不會影響從這個類中派生的其餘對象。

!(function () {
    var plance = {
        fire:function(){
            console.log("發射普通子彈");
        }
    };
    var missileDecorator = function(){
        console.log("發射導彈");
    };
    var fire1 = plance.fire;
    plance.fire = function(){
        fire1();
        missileDecorator();
    };
    plance.fire();
})()
裝飾者模式

或者使用裝飾函數(AOP)Function的after或者before

!(function () {
    var plance = function () { };
    plance.prototype.fire = function () {
        console.log("發射普通子彈");
    };
    var missileDecorator = function () {
        console.log("發射導彈");
    };
    Function.prototype.after = function (afterfn) {
        var _self = this;
        return function () {
            var ret = _self.apply(this, arguments);
            afterfn.apply(this, arguments);
            return ret;
        };
    };
    var pl = new plance();
    pl.fire = pl.fire.after(missileDecorator);
    pl.fire();
})()
使用AOP

裝飾函數是一個很實用的功能,例如咱們製做插件式的表單驗證,就可使用裝飾函數。

!(function () {
    var registerForm = document.getElementById("registerForm");
    Function.prototype.before = function(beforeFn){
        var _self = this;
        return function(){
            if(beforeFn.apply(this,arguments) === false){
                return;
            }
            return _self.apply(this,arguments);
        };
    }
    var validata = function () {
        if(registerForm.userName.value === ""){
            alert("用戶名不能爲空");
            return false;
        }
    };
    var formSubmit = function(){
        console.log("成功");
    }
    formSubmit = formSubmit.before(validata);
    registerForm.onsubmit = function(){
        formSubmit();
        return false;
    };
})()
使用before登陸驗證

代理模式OR裝飾器模式

代理模式和裝飾器模式類似的地方不少,都是有單獨的一個對象提供間接訪問。他們最大的不一樣就是設計的意圖和目的。

代理模式的目的是:當直接訪問本體不方便或者不符合需求時,爲這個本體提供一個替代者。

裝飾器模式的目的是:爲對象動態加入一些行爲。

例如圖片預加載,代理提供預加載功能是調用原來的方法也就是跟本體作的事情同樣,而裝飾器模式則是添加新的職責和行爲。

狀態模式

狀態模式關鍵是區分事物內部的狀態,事物內部狀態改變會帶來事物行爲的改變。

第一個例子:電燈開關,一樣是按下開關,電燈亮或者不亮,表達的行爲是不同的。buttonWasPressed 方法是變化的點。

!(function () {
    var LightEvent = {
        on:function(){
            console.log("關燈");
            this.state = "off";
        },
        off:function(){
            console.log("開燈");
            this.state = "on";
        }
    };
    var Light = function () {
        this.state = "off";
        this.button = null;
    };
    Light.prototype.init = function(){
        var button = document.createElement("button"),self=this;
        button.innerHTML = "開關";
        this.button = document.body.appendChild(button);
        this.button.onclick = function(){
            self.buttonWasPressed();
        };
    };
    Light.prototype.buttonWasPressed = function(){
        LightEvent[this.state].apply(this,arguments);
    };
    var light = new Light();
    light.init();
})()
電燈例子

上一個例子使用策略模式來完成的,咱們來講一下策略模式與狀態模式的區別

策略模式中各個策略類是平等又平行的,他們之間沒有任何聯繫。狀態類的行爲早已被封裝好了,改變行爲發生在狀態模式內部。

那麼我是使用狀態模式,來完成上面電燈的例子。

!(function () {
    var delegate = function(client,delegation){
        return {
            buttonWasPressed:function(){
                return delegation.buttonWasPressed.apply(client,arguments);
            }
        };
    };
    var FSM = {
        off: {
            buttonWasPressed: function () {
                console.log("關燈");
                this.currState = this.onState;
            }
        },
        on: {
            buttonWasPressed: function () {
                console.log("開燈");
                this.currState = this.offState;
            }
        }
    };
    var Light = function () {
        this.offState = delegate(this,FSM.off);
        this.onState = delegate(this,FSM.on);
        this.currState = FSM.off;
        this.button = null;
    };
    Light.prototype.init = function () {
        var button = document.createElement("button"), self = this;
        button.innerHTML = "開關";
        this.button = document.body.appendChild(button);
        this.button.onclick = function () {
            self.currState.buttonWasPressed.call(self);
        };
    };
    var light = new Light();
    light.init();
})()
狀態機電燈例子
相關文章
相關標籤/搜索