單例模式的核心:
(1)確保只有一個實例
(2)提供全局訪問javascript
用代理實現單例模式:html
var ProxySingletonCreateDiv = (function(){ var instance; return function( html ){ if ( !instance ){ instance = new CreateDiv( html ); } return instance; } })();
通用的惰性單例模式:建立登錄懸浮窗java
//fn保存建立邏輯 //單例模式:只建立一次 var getSingle = function( fn ){ var result; return function(){ return result || ( result = fn .apply(this, arguments ) ); } }; var createLoginLayer = function(){ var div = document.createElement( 'div' ); div.innerHTML = '我是登陸浮窗'; div.style.display = 'none'; document.body.appendChild( div ); return div; }; var createSingleLoginLayer = getSingle( createLoginLayer ); document.getElementById( 'loginBtn' ).onclick = function(){ var loginLayer = createSingleLoginLayer(); loginLayer.style.display = 'block'; };
(1)首先要指定好誰充當發佈者
(2)而後給發佈者添加一個緩存列表,用於存放回調函數以便通知訂閱者
(3)最後發佈消息的時候,發佈者會遍歷這個緩存列表,依次觸發裏面存放的訂閱者函數算法
var Event = (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 } })();
給全部的對象都動態安裝一個發佈-訂閱功能:緩存
var installEvent = function( obj ){ for ( var i in event ){ obj[ i ] = event[ i ]; } };
(1)裝飾者模式能夠動態的給某個對象添加一些額外的職責,而不會影響從這個類中派生出的其餘對象。
(2)裝飾者也是包裝器:裝飾者模式將一個對象嵌入到另外一個對象中,實際上至關於這個對象被另外一個對象包裝起來,造成一條包裝鏈。app
3-1.在不改變原來函數的狀況下給函數增長新的功能dom
var a=function(){ alert(1); } var _a = a; a=function(){ _a(); alert(2); }
可是這樣作會帶來兩個問題:必須維護_a這個中間變量;存在this被劫持的問題
3-2.用AOP裝飾函數函數
Function.prototype.before = function( beforefn ){ var __self = this; // 保存原函數的引用 return function(){ // 返回包含了原函數和新函數的"代理"函數 beforefn.apply( this, arguments ); // 執行新函數,且保證this 不被劫持,新函數接受的參數 // 也會被原封不動地傳入原函數,新函數在原函數以前執行 return __self.apply( this, arguments ); // 執行原函數並返回原函數的執行結果, // 而且保證this 不被劫持 } } Function.prototype.after = function( afterfn ){ var __self = this; return function(){ var ret = __self.apply( this, arguments ); afterfn.apply( this, arguments ); return ret; } }; //調用:重寫原來函數 formSubmit = formSubmit.before( validata );
策略模式的定義是:定義一系列的算法,把它們一個個封裝起來,而且使它們能夠相互替換。
4-1.使用策略模式計算獎金
績效爲S的人年終獎金是工資的4倍,績效爲A的人年終獎金是工資的3倍,績效爲B的人年終獎金是工資的2倍post
var performanceS = function(){}; performanceS.prototype.calculate = function( salary ){ return salary * 4; }; var performanceA = function(){}; performanceA.prototype.calculate = function( salary ){ return salary * 3; }; var performanceB = function(){}; performanceB.prototype.calculate = function( salary ){ return salary * 2; }; //接下來定義獎金類Bonus: var Bonus = function(){ this.salary = null; // 原始工資 this.strategy = null; // 績效等級對應的策略對象 }; Bonus.prototype.setSalary = function( salary ){ this.salary = salary; // 設置員工的原始工資 }; Bonus.prototype.setStrategy = function( strategy ){ this.strategy = strategy; // 設置員工績效等級對應的策略對象 }; Bonus.prototype.getBonus = function(){ // 取得獎金數額 return this.strategy.calculate( this.salary ); // 把計算獎金的操做委託給對應的策略對象 }; var bonus = new Bonus(); bonus.setSalary( 10000 ); bonus.setStrategy( new performanceS() ); // 設置策略對象 console.log( bonus.getBonus() ); // 輸出:40000
4-2.javascript版本的策略模式this
var strategies = { "S": function( salary ){ return salary * 4; }, "A": function( salary ){ return salary * 3; }, "B": function( salary ){ return salary * 2; } }; var calculateBonus = function( level, salary ){ return strategies[ level ]( salary ); }; console.log( calculateBonus( 'S', 20000 ) ); // 輸出:80000 console.log( calculateBonus( 'A', 10000 ) ); // 輸出:30000
經過使用策略模式重構代碼,咱們消除了原來程序中大片的條件分支代碼。全部和計算獎金的邏輯再也不放在context中,而是分佈在各個策略對象中。每一個策略對象負責的算法已被各自封裝在對象內部。當咱們對這些策略對象發出「計算獎金」請求時,它們會返回各自不一樣的計算結果。
4-3.用策略模式進行表單校驗
(1)用戶名不能爲空
(2)密碼長度不能少於6位
(3)手機號碼必須符合格式
<html> <body> <form action="http:// xxx.com/register" id="registerForm" method="post"> 請輸入用戶名:<input type="text" name="userName"/ > 請輸入密碼:<input type="text" name="password"/ > 請輸入手機號碼:<input type="text" name="phoneNumber"/ > <button>提交</button> </form> <script> /***********************策略對象**************************/ 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; } } }; /***********************Validator 類**************************/ 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 strategyAry = rule.strategy.split( ':' ); var errorMsg = rule.errorMsg; self.cache.push(function(){ var strategy = strategyAry.shift(); strategyAry.unshift( dom.value ); strategyAry.push( errorMsg ); return strategies[ strategy ].apply( dom, strategyAry ); }); })( rule ) } }; Validator.prototype.start = function(){ for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){ var errorMsg = validatorFunc(); if ( errorMsg ){ return errorMsg; } } }; /***********************客戶調用代碼**************************/ var registerForm = document.getElementById( 'registerForm' ); var validataFunc = function(){ var validator = new Validator(); validator.add( registerForm.userName, [{ strategy: 'isNonEmpty', errorMsg: '用戶名不能爲空' }, { strategy: 'minLength:6', errorMsg: '用戶名長度不能小於10 位' }]); validator.add( registerForm.password, [{ strategy: 'minLength:6', errorMsg: '密碼長度不能小於6 位' }]); var errorMsg = validator.start(); return errorMsg; } registerForm.onsubmit = function(){ var errorMsg = validataFunc(); if ( errorMsg ){ alert ( errorMsg ); return false; } }; </script> </body> </html>
代理模式是爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。
5-1.小明追女神的故事
小明遇到了他的女神A,打算送一朵鮮花來表白。恰好小明打聽到他和A有一個共同的好友B,因而小明決定讓B來替本身來完成這件事。
var Flower = function(){}; var xiaoming = { sendFlower: function( target ){ var flower = new Flower(); target.receiveFlower( flower ); } }; var A = { receiveFlower: function( flower ){ console.log( '收到花 ' + flower ); } }; xiaoming.sendFlower( A ); //接下來,咱們引入代理B,即小明經過B 來給A 送花: var Flower = function(){}; var xiaoming = { sendFlower: function( target){ var flower = new Flower(); target.receiveFlower( flower ); } }; var B = { receiveFlower: function( flower ){ A.receiveFlower( flower ); 90 第6 章 代理模式 } }; var A = { receiveFlower: function( flower ){ console.log( '收到花 ' + flower ); } }; xiaoming.sendFlower( B ); //而後選擇A 心情好的時候把花轉交給A,代碼以下: var Flower = function(){}; var xiaoming = { sendFlower: function( target){ var flower = new Flower(); target.receiveFlower( flower ); } }; var B = { receiveFlower: function( flower ){ A.listenGoodMood(function(){ // 監聽A 的好心情 A.receiveFlower( flower ); }); } }; var A = { receiveFlower: function( flower ){ console.log( '收到花 ' + flower ); }, listenGoodMood: function( fn ){ setTimeout(function(){ // 假設10 秒以後A 的心情變好 fn(); }, 10000 ); } }; xiaoming.sendFlower( B ); var myImage = (function(){ var imgNode = document.createElement( 'img' ); document.body.appendChild( imgNode ); return { setSrc: function( src ){ imgNode.src = src; } } })();
5-2.保護代理與虛擬代理
保護代理:好比B能夠幫助A過濾掉一些請求,如送花的人中年齡較大的
虛擬代理:new Flower多是很是昂貴的,這時候須要B代理去執行,代理B在A心情好的時候再執行。
5-3.虛擬代理實現圖片預加載
var myImage = (function(){ var imgNode = document.createElement( 'img' ); document.body.appendChild( imgNode ); return function( src ){ imgNode.src = src; } })(); var proxyImage = (function(){ var img = new Image; img.onload = function(){ myImage( this.src ); } return function( src ){ myImage( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' ); img.src = src; } })(); proxyImage( 'http:// imgcache.qq.com/music// N/k/000GGDys0yA0Nk.jpg' );
縱觀整個程序,咱們並無改變或者增長myImage接口,可是經過代理對象,其實是給系統添加了新的行爲:給img節點設置 src和圖片預加載這兩個功能。
5-4.虛擬代理合並http請求
<body> <input type="checkbox" id="1"></input>1 <input type="checkbox" id="2"></input>2 <input type="checkbox" id="3"></input>3 <input type="checkbox" id="4"></input>4 <input type="checkbox" id="5"></input>5 <input type="checkbox" id="6"></input>6 <input type="checkbox" id="7"></input>7 <input type="checkbox" id="8"></input>8 <input type="checkbox" id="9"></input>9 </body>
script.js:
var synchronousFile = function( id ){ console.log( '開始同步文件,id 爲: ' + id ); }; var proxySynchronousFile = (function(){ var cache = [], // 保存一段時間內須要同步的ID timer; // 定時器 return function( id ){ cache.push( id ); if ( timer ){ // 保證不會覆蓋已經啓動的定時器 return; } timer = setTimeout(function(){ synchronousFile( cache.join( ',' ) ); // 2 秒後向本體發送須要同步的ID 集合 clearTimeout( timer ); // 清空定時器 timer = null; cache.length = 0; // 清空ID 集合 }, 2000 ); } })(); var checkbox = document.getElementsByTagName( 'input' ); for ( var i = 0, c; c = checkbox[ i++ ]; ){ c.onclick = function(){ if ( this.checked === true ){ proxySynchronousFile( this.id ); } } };