Javascript設計模式記錄,這個方面確實是沒寫過,工做中也沒有用到js設計模式的地方。javascript
使用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這種寫法。數組
繼承關係的兩個對象,在實例的過程當中,能夠經過修改指向,來調整調用構造函數。緩存
!(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時,循環會終止。
!(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; } }; })()
果真,有邏輯的js,很是好玩。比CSS好玩多了。感受有不少委託、多態的思想。
策略模式的優勢與缺點
缺點
爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。當客戶不方便直接訪問一個對象的時候,須要提供一個替身對象來控制對這個對象的訪問。
代理模式分爲:虛擬代理和保護代理
虛擬代理:把一些開銷很大的對象,延遲到真正須要它的時候纔去建立。
保護代理:用於控制不一樣權限的對象對目標對象的訪問。
使用虛擬代理能夠完成圖片預加載功能,先用一張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(); })()
裝飾函數是一個很實用的功能,例如咱們製做插件式的表單驗證,就可使用裝飾函數。
!(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; }; })()
代理模式和裝飾器模式類似的地方不少,都是有單獨的一個對象提供間接訪問。他們最大的不一樣就是設計的意圖和目的。
代理模式的目的是:當直接訪問本體不方便或者不符合需求時,爲這個本體提供一個替代者。
裝飾器模式的目的是:爲對象動態加入一些行爲。
例如圖片預加載,代理提供預加載功能是調用原來的方法也就是跟本體作的事情同樣,而裝飾器模式則是添加新的職責和行爲。
狀態模式關鍵是區分事物內部的狀態,事物內部狀態改變會帶來事物行爲的改變。
第一個例子:電燈開關,一樣是按下開關,電燈亮或者不亮,表達的行爲是不同的。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(); })()