記得剛畢業的時候參加了一次校招面試,以前表現的很好,最後時面試官問我懂不懂設計模式,我說不懂,而後就進去了;後面又參加了某大公司的校招,開始表現還行,後面面試官問我懂不懂設計模式,我說懂(上次後補習了下),最後把工廠模式的代碼背寫到了紙上,而後就沒有而後了......javascript
如今回想起來當時有點傻有點天真,沒有幾十萬的代碼量,沒有必定的經驗總結,竟然敢說懂設計模式,這不是找抽麼?css
通過這幾年工做學習,感受是時候系統的回憶下平時工做內容,看用到了什麼設計模式,權當總結。html
小釵對設計模式的理解程度有限,文中不足之處請您拍磚。前端
設計模式即是面向對象的深刻,面向對象的應用,因此類的實現是第一步:java
PS:這裏依賴了underscore,各位本身加上吧。android
1 //window._ = _ || {}; 2 // 全局可能用到的變量 3 var arr = []; 4 var slice = arr.slice; 5 /** 6 * inherit方法,js的繼承,默認爲兩個參數 7 * 8 * @param {function} origin 可選,要繼承的類 9 * @param {object} methods 被建立類的成員,擴展的方法和屬性 10 * @return {function} 繼承以後的子類 11 */ 12 _.inherit = function (origin, methods) { 13 14 // 參數檢測,該繼承方法,只支持一個參數建立類,或者兩個參數繼承類 15 if (arguments.length === 0 || arguments.length > 2) throw '參數錯誤'; 16 17 var parent = null; 18 19 // 將參數轉換爲數組 20 var properties = slice.call(arguments); 21 22 // 若是第一個參數爲類(function),那麼就將之取出 23 if (typeof properties[0] === 'function') 24 parent = properties.shift(); 25 properties = properties[0]; 26 27 // 建立新類用於返回 28 function klass() { 29 if (_.isFunction(this.initialize)) 30 this.initialize.apply(this, arguments); 31 } 32 33 klass.superclass = parent; 34 35 // 父類的方法不作保留,直接賦給子類 36 // parent.subclasses = []; 37 38 if (parent) { 39 // 中間過渡類,防止parent的構造函數被執行 40 var subclass = function () { }; 41 subclass.prototype = parent.prototype; 42 klass.prototype = new subclass(); 43 44 // 父類的方法不作保留,直接賦給子類 45 // parent.subclasses.push(klass); 46 } 47 48 var ancestor = klass.superclass && klass.superclass.prototype; 49 for (var k in properties) { 50 var value = properties[k]; 51 52 //知足條件就重寫 53 if (ancestor && typeof value == 'function') { 54 var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(','); 55 //只有在第一個參數爲$super狀況下才須要處理(是否具備重複方法須要用戶本身決定) 56 if (argslist[0] === '$super' && ancestor[k]) { 57 value = (function (methodName, fn) { 58 return function () { 59 var scope = this; 60 var args = [ 61 function () { 62 return ancestor[methodName].apply(scope, arguments); 63 } 64 ]; 65 return fn.apply(this, args.concat(slice.call(arguments))); 66 }; 67 })(k, value); 68 } 69 } 70 71 //此處對對象進行擴展,當前原型鏈已經存在該對象,便進行擴展 72 if (_.isObject(klass.prototype[k]) && _.isObject(value) && (typeof klass.prototype[k] != 'function' && typeof value != 'fuction')) { 73 //原型鏈是共享的,這裏處理邏輯要改 74 var temp = {}; 75 _.extend(temp, klass.prototype[k]); 76 _.extend(temp, value); 77 klass.prototype[k] = temp; 78 } else { 79 klass.prototype[k] = value; 80 } 81 82 } 83 84 if (!klass.prototype.initialize) 85 klass.prototype.initialize = function () { }; 86 87 klass.prototype.constructor = klass; 88 89 return klass; 90 };
使用測試:ios
1 var Person = _.inherit({ 2 initialize: function(opts) { 3 this.setOpts(opts); 4 }, 5 6 setOpts: function (opts) { 7 for(var k in opts) { 8 this[k] = opts[k]; 9 } 10 }, 11 12 getName: function() { 13 return this.name; 14 }, 15 16 setName: function (name) { 17 this.name = name 18 } 19 }); 20 21 var Man = _.inherit(Person, { 22 initialize: function($super, opts) { 23 $super(opts); 24 this.sex = 'man'; 25 }, 26 27 getSex: function () { 28 return this.sex; 29 } 30 }); 31 32 var Woman = _.inherit(Person, { 33 initialize: function($super, opts) { 34 $super(opts); 35 this.sex = 'women'; 36 }, 37 38 getSex: function () { 39 return this.sex; 40 } 41 }); 42 43 var xiaoming = new Man({ 44 name: '小明' 45 }); 46 47 var xiaohong = new Woman({ 48 name: '小紅' 49 });
xiaoming.getName() "小明" xiaohong.getName() "小紅" xiaoming.getSex() "man" xiaohong.getSex() "women"
單列爲了保證一個類只有一個實例,若是不存在便直接返回,若是存在便返回上一次的實例,其目的通常是爲了資源優化。web
javascript中實現單例的方式比較多,比較實用的是直接使用對象字面量:面試
1 var singleton = { 2 property1: "property1", 3 property2: "property2", 4 method1: function () {} 5 };
類實現是正統的實現,通常是放到類上,作靜態方法:數據庫
在實際項目中,通常這個應用會在一些通用UI上,好比mask,alert,toast,loading這類組件,還有多是一些請求數據的model,簡單代碼以下:
1 //惟一標識,通常在amd模塊中 2 var instance = null; 3 4 //js不存在多線程,這裏是安全的 5 var UIAlert = _.inherit({ 6 initialize: function(msg) { 7 this.msg = msg; 8 }, 9 setMsg: function (msg) { 10 this.msg = msg; 11 }, 12 showMessage: function() { 13 console.log(this.msg); 14 } 15 }); 16 17 var m1 = new UIAlert('1'); 18 m1.showMessage();//1 19 var m2 = new UIAlert('2'); 20 m2.showMessage();//2 21 m1.showMessage();//1
如所示,這個是一個簡單的應用,若是稍做更改的話:
1 //惟一標識,通常在amd模塊中 2 var instance = null; 3 4 //js不存在多線程,這裏是安全的 5 var UIAlert = _.inherit({ 6 initialize: function(msg) { 7 this.msg = msg; 8 }, 9 setMsg: function (msg) { 10 this.msg = msg; 11 }, 12 showMessage: function() { 13 console.log(this.msg); 14 } 15 }); 16 UIAlert.getInstance = function () { 17 if (instance instanceof this) { 18 return instance; 19 } else { 20 return instance = new UIAlert(); //new this 21 } 22 } 23 24 var m1 = UIAlert.getInstance(); 25 m1.setMsg(1); 26 m1.showMessage();//1 27 var m2 = UIAlert.getInstance(); 28 m2.setMsg(2); 29 m2.showMessage();//2 30 m1.showMessage();//2
如所示,第二次的改變,影響了m1的值,由於他們的實例是共享的,這個即是一次單例的使用,而實際場景複雜得多。
以alert組件爲例,他還會存在按鈕,一個、兩個或者三個,每一個按鈕事件回調不同,一次設置後,第二次使用時各個事件也須要被重置,好比事件裝在一個數組eventArr = []中,每次這個數組須要被清空重置,整個組件的dom結構也會重置,好像這個單例的意義也減少了,真實的意義在於全站,特別是對於webapp的網站,只有一個UI dom的根節點,這個纔是該場景的意義所在。
而對mask而言便不太適合所有作單例,以彈出層UI來講,通常都會帶有一個mask組件,若是一個組件彈出後立刻再彈出一個,第二個mask若是與第一個共享的話便不合適了,由於這個mask應該是各組件獨享的。
單例在javascript中的應用更多的仍是來劃分命名空間,好比underscore庫,好比如下場景:
① Hybrid橋接的代碼
window.Hybrid = {};//存放全部Hybrid的參數
② 日期函數
window.DateUtil = {};//存放一些日期操做方法,好比將「2015年2月14日」這類字符串轉換爲日期對象,或者逆向轉換
......
工廠模式是一個比較經常使用的模式,介於javascript對象的不定性,其在前端的應用門檻更低。
工廠模式出現之初意在解決對象耦合問題,經過工廠方法,而不是new關鍵字實例化具體類,將全部可能的類的實例化集中在一塊兒。
一個最經常使用的例子即是咱們的Ajax模塊:
1 var XMLHttpFactory = {}; 2 var XMLHttpFactory.createXMLHttp = function() { 3 var XMLHttp = null; 4 if (window.XMLHttpRequest){ 5 XMLHttp = new XMLHttpRequest() 6 }else if (window.ActiveXObject){ 7 XMLHttp = new ActiveXObject("Microsoft.XMLHTTP") 8 } 9 return XMLHttp; 10 }
使用工廠方法的前提是,產品類的接口須要一致,至少公用接口是一致的,好比咱們這裏有一個需求是這樣的:
能夠看到各個模塊都是不同的:
① 數據請求
② dom渲染,樣式也有所不一樣
③ 事件交互
可是他們有同樣是相同的:會有一個共同的事件點:
① create
② show
③ hide
因此咱們的代碼能夠是這樣的:
1 var AbstractView = _.inherit({ 2 initialize: function() { 3 this.wrapper = $('body'); 4 //事件管道,實例化時觸發onCreate,show時候觸發onShow...... 5 this.eventsArr = []; 6 }, 7 show: function(){}, 8 hide: function (){} 9 }); 10 var SinaView = _.inherit(AbstractView, { 11 }); 12 var BaiduView = _.inherit(AbstractView, { 13 });
每個組件實例化只須要執行實例化操做與show操做便可,各個view的顯示邏輯在本身的事件管道實現,真實的邏輯多是這樣的
1 var ViewContainer = { 2 SinaView: SinaView, 3 BaiduView: BaiduView 4 }; 5 var createView = function (view, wrapper) { 6 //這裏會有一些監測工做,事實上全部的view類應該放到一個單列ViewContainer中 7 var ins = new ViewContainer[view + 'View']; 8 ins.wrapper = wrapper; 9 ins.show(); 10 } 11 //數據庫讀出數據 12 var moduleInfo = ['Baidu', 'Sina', '...']; 13 14 for(var i = 0, len = moduleInfo.length; i < len; i++){ 15 createView(moduleInfo[i]); 16 }
如以前寫的坦克大戰,建立各自坦克工廠模式也是絕佳的選擇,工廠模式暫時到此。
橋接模式一個很是典型的使用即是在Hybrid場景中,native同事會給出一個用於橋接native與H5的模塊,通常爲bridge.js。
native與H5原本就是互相獨立又互相變化的,如何在多個維度的變化中又不引入額外複雜度,這個時候bridge模式便派上了用場,使抽象部分與實現部分分離,各自便能獨立變化。
這裏另舉一個應用場景,即是UI與其動畫類,UI通常會有show的動做,一般便直接顯示了出來,可是咱們實際工做中須要的UI顯示是:由下向上動畫顯示,由上向下動畫顯示等效果。
這個時候咱們應該怎麼處理呢,簡單設計一下:
1 var AbstractView = _.inherit({ 2 initialize: function () { 3 //這裏的dom其實應該由template於data組成,這裏簡化 4 this.$el = $('<div style="display: none; position: absolute; left: 100px; top: 100px; border: 1px solid #000000;">組件</div>'); 5 this.$wrapper = $('body'); 6 this.animatIns = null; 7 }, 8 show: function () { 9 this.$wrapper.append(this.$el); 10 if(!this.animatIns) { 11 this.$el.show(); 12 } else { 13 this.animatIns.animate(this.$el, function(){}); 14 } 15 //this.bindEvents(); 16 } 17 }); 18 19 var AbstractAnimate = _.inherit({ 20 initialize: function () { 21 }, 22 //override 23 animate: function (el, callback) { 24 el.show(); 25 callback(); 26 } 27 }); 28 29 30 var UPToDwonAnimate = _.inherit(AbstractAnimate, { 31 animate: function (el, callback) { 32 //動畫具體實現不予關注,這裏使用zepto實現 33 el.animate({ 34 'transform': 'translate(0, -250%)' 35 }).show().animate({ 36 'transform': 'translate(0, 0)' 37 }, 200, 'ease-in-out', callback); 38 } 39 }); 40 41 42 var UIAlert = _.inherit(AbstractView, { 43 initialize: function ($super, animateIns) { 44 $super(); 45 this.$el = $('<div style="display: none; position: absolute; left: 100px; top: 200px; border: 1px solid #000000;">alert組件</div>'); 46 this.animatIns = animateIns; 47 } 48 }); 49 50 var UIToast = _.inherit(AbstractView, { 51 initialize: function ($super, animateIns) { 52 $super(); 53 this.animatIns = animateIns; 54 } 55 }); 56 57 var t = new UIToast(new UPToDwonAnimate); 58 t.show(); 59 60 var a = new UIAlert(); 61 a.show();
這裏組件對動畫類庫有依賴,可是各自又不互相影響(事實上仍是有必定影響的,好比其中一些事件便須要動畫參數觸發),這個即是一個典型的橋接模式。
再換個方向理解,UI的css樣式事實上也能夠作到兩套系統,一套dom結構一套皮膚庫,可是這個實現上有點複雜,由於html不可分割,而動畫功能這樣處理卻比較合適。
裝飾者模式的意圖是爲一個對象動態的增長一些額外職責;是類繼承的另一種選擇,一個是編譯時候增長行爲,一個是運行時候。
裝飾者要求其實現與包裝的對象統一,並作到過程透明,意味着能夠用他來包裝其餘對象,而使用方法與原來一致。
一次邏輯的執行能夠包含多個裝飾對象,這裏舉個例子來講,在webapp中每一個頁面的view每每會包含一個show方法,而在咱們的頁面中咱們可能會根據localsorage或者ua判斷要不要顯示下面廣告條,效果以下:
那麼這個邏輯應該如何實現呢?
1 var View = _.inherit({ 2 initialize: function () {}, 3 show: function () { 4 console.log('渲染基本頁面'); 5 } 6 }); 7 8 //廣告裝飾者 9 var AdDecorator = _.inherit({ 10 initialize: function (view) { 11 this.view = view; 12 }, 13 show: function () { 14 this.view.show(); 15 console.log('渲染廣告區域'); 16 } 17 }); 18 19 //基本使用 20 var v = new View(); 21 v.show(); 22 23 //........ .知足必定條件........... 24 var d = new AdDecorator(v); 25 d.show();
說實話,就站在前端的角度,以及個人視野來講,這個裝飾者其實不太實用,換個說法,這個裝飾者模式很是相似面向切口編程,就是在某一個點前作點事情,後作點事情,這個時候事件管道彷佛更加合適。
組合模式是前端比較經常使用的一個模式,目的是解耦複雜程序的內部結構,更業務一點即是將一個複雜組件分紅多個小組件,最後保持使用時單個對象和組合對象具備一致性。
假如我這裏有一個彈出層容器組件,其內部會有三個select組件,他是這個樣子的:
如所見,該組件內部有三個可拖動組件select組件,單獨以select的實現便很是複雜,若是一個獨立組件要實現該功能便十分讓人頭疼,外彈出層還設計蒙版等交互,便很是複雜了,那麼這個該如何拆分呢?
事實上這裏要表達的意思是ui.layer.Container保存着對select組件的依賴,只不過這個UML圖是基於強類型語言而出,js並不必定徹底一致。
1 var AbstractView = _.inherit({ 2 initialize: function () { 3 this.wrapper = 'body' 4 this.name = '抽象類'; 5 }, 6 show: function () { 7 console.log('在' + this.wrapper + '中,顯示組件:' + this.name); 8 } 9 }); 10 11 //select組件,事實上基礎渲染的工做抽象類應該所有作掉 12 var UISelect = _.inherit(AbstractView, { 13 initialize: function ($super) { 14 $super(); 15 this.name = 'select組件' 16 // this.id = ''; 17 // this.value = ''; 18 //當前選項 19 this.index = 0; 20 //事實上會根據此數據生產完整組件 21 this.data = []; 22 this.name = 'select組件'; 23 } 24 }); 25 26 var UILayerContainer = _.inherit(AbstractView, { 27 initialize: function ($super) { 28 $super(); 29 this.name = 'select容器' 30 this.selectArr = []; 31 }, 32 add: function(select) { 33 if(select instanceof UISelect) this.selectArr.push(select); 34 },//增長一項 35 remove: function(select){},//移除一項 36 //容器組件顯示的同時,須要將包含對象顯示 37 show: function ($super) { 38 $super(); 39 for(var i = 0, len = this.selectArr.length; i < len; i++){ 40 this.selectArr[i].wrapper = this.name; 41 this.selectArr[i].show(); 42 } 43 } 44 }); 45 46 var s1 = new UISelect(); 47 var s2 = new UISelect(); 48 49 var c = new UILayerContainer(); 50 c.add(s1); 51 c.add(s2); 52 53 c.show(); 54 /* 55 在body中,顯示組件:select容器 01.html:113 56 在select容器中,顯示組件:select組件 57 在select容器中,顯示組件:select組件 58 */
怎麼說呢,真實的使用場景確定會有所不一樣,咱們不會在容器外實例化select組件,而是直接在其內部完成;組合模式在工做中是比較經常使用的,並且容器組件未必會有add,remove等實現,每每只是要求你初始化時能將其內部組件顯示好就行。
門面模式又稱爲外觀模式,旨在爲子系統提供一致的界面,門面模式提供一個高層的接口,這個接口使得子系統更加容易使用;若是沒有外觀模式用戶便會直接調用子系統,那麼用戶必須知道子系統更多的細節,而可能形成麻煩與不便。
我對該模式比較印象深入是因爲一次框架的誤用,當時作Hybrid開發時,在手機App中嵌入H5程序,經過js調用native接口發生通訊,從而突破瀏覽器限制。
如圖所示,當時的想法是,全部業務同事使用native api所有走框架提供的facade層,而不用去關心真實底層的實現,可是當時有點過分設計,作出來的門面有點「太多了」,這是我當時老大的一段代碼:
1 var prototype = require('prototype'); 2 3 var UrlSchemeFacade = prototype.Class.create({ 4 5 nativeInterfaceMap: { 6 'geo.locate': 'ctrip://wireless/geo/locate', 7 'device.info': 'ctrip://wireless/device/info' 8 }, 9 10 getUrlScheme: function(key) { 11 return this.nativeInterfaceMap[key]; 12 } 13 14 }); 15 16 UrlSchemeFacade.API = { 17 'GEOLOCATE':'geo.locate', 18 'DEVICEINFO': 'device.info' 19 } 20 21 var HybridBridge = prototype.Class.create({ 22 23 initialize: function(facade) { 24 this.urlSchemeFacade = facade; 25 }, 26 27 request: function(api) { 28 var url = this.urlSchemeFacade.getUrlScheme(api); 29 console.log(url); 30 31 // @todo 調用url scheme 32 // window.location.replace = url; 33 } 34 35 }); 36 37 var Main = function () { 38 var urlSchemeFacade = new UrlSchemeFacade(); 39 var hybridBridge = new HybridBridge(urlSchemeFacade); 40 41 hybridBridge.request(UrlSchemeFacade.API.GEOLOCATE); 42 } 43 44 Main();
如所示,這裏存在一個與native方法api的一個映射,這個意味着咱們爲每個方法提供了一個門面?而咱們並不知道native會提供多少方法,因而native一旦新增api,咱們的門面方法也須要新增,這個是不正確的。
好的作法是應該是像封裝Ajax,或者封裝addEventListener同樣,門面須要提供,可是不該該細化到接口,想象一下,若是咱們對全部的事件類型若是都提供門面,那麼這個門面該有多難用。
如圖所示,真正的門面不該該包含getAddress這一層,而應該將之做爲參數傳入,代碼如:
1 window.Hybrid = {}; 2 3 //封裝統一的發送url接口,解決ios、android兼容問題,這裏發出的url會被攔截,會獲取其中參數,好比: 4 //這裏會獲取getAdressList參數,調用native接口回去通信錄數據,造成json data數據,拿到webview的window執行,window.Hybrid['hybrid12334'](data) 5 var bridgePostMessage = function (url) { 6 if (isIOS()) { 7 window.location = url; 8 } if (isAndriond()) { 9 var ifr = $('<iframe src="' + url + '"/>'); 10 $('body').append(ifr); 11 } 12 }; 13 14 //根據參數返回知足Hybrid條件的url,好比taobao://getAdressList?callback=hybrid12334 15 var _getHybridUrl = function (params) { 16 var url = ''; 17 //...aa操做paramss生成url 18 return url; 19 }; 20 21 //頁面級用戶調用的方法 22 var HybridFacadeRequest = function (params) { 23 //其它操做...... 24 25 //生成惟一執行函數,執行後銷燬 26 var t = 'hybrid_' + (new Date().getTime()); 27 //處理有回調的狀況 28 if (params.callback) { 29 window.Hybrid[t] = function (data) { 30 params.callback(data); 31 delete window.Hybrid[t]; 32 } 33 } 34 35 bridgePostMessage(_getHybridUrl(params)) 36 }; 37 38 //h5頁面開發,調用Hybrid接口,獲取通信錄數據 39 define([], function () { 40 return function () { 41 //業務實際調用點 42 HybridFacadeRequest({ 43 //native標誌位 44 tagname: 'getAdressList', 45 //返回後執行回調函數 46 callback: function (data) { 47 //處理data,生成html結構,裝載頁面 48 } 49 }); 50 } 51 });
封裝調用子系統的實現,可是不喜歡映射到最終的api,這裏不對請您拍磚。
適配器模式的目的是將一類接口轉換爲用戶但願的另一種接口,使本來不兼容的接口能夠一塊兒工做。
事實上這種模式一旦使用可能就面臨第三方或者其它模塊要與你的模塊一塊兒使用的需求發生了,這個在.net的數據訪問模塊dataAdapter也在使用。
這個模式通常是這麼個狀況,好比最初咱們使用的是本身的loading組件,可是如今出了一個情感化loading組件,而這個組件是由其它團隊提供,接口與咱們徹底不一致,這個時候便須要適配器模式的出現了。
如圖示,雖然loading組件與情感化loading組件的接口徹底不一致,可是他們必須是乾的一件事情,若是乾的事情也不同,那麼就完不了了......
1 var UILoading = _.inherit({ 2 initialize: function () { 3 console.log('初始化loading組件dom結構') 4 }, 5 show: function () { 6 console.log('顯示loading組件'); 7 } 8 }); 9 10 var EmotionLoading = function() { 11 console.log('初始化情感化組件'); 12 }; 13 EmotionLoading.prototype.init = function () { 14 console.log('顯示情感化組件'); 15 }; 16 17 var LoadingAdapter = _.inherit(UILoading, { 18 initialize: function (loading) { 19 this.loading = loading; 20 }, 21 show: function () { 22 this.loading.init(); 23 } 24 }) 25 26 var l1 = new UILoading(); 27 l1.show(); 28 29 var l2 = new LoadingAdapter(new EmotionLoading()); 30 l2.show(); 31 32 /* 33 初始化loading組件dom結構 01.html:110 34 顯示loading組件 01.html:113 35 初始化情感化組件 01.html:118 36 顯示情感化組件 37 */
代理模式即是棒別人作事,爲其餘對象提供一種代理,以控制對這個對象的訪問,最經典的用法即是:
$.proxy(function() {}, this);
能夠看到,最終作的人,依舊是本身,只不過別人覺得是代理對象作的,這個有點相似於爲人做嫁衣;固然,也能夠理解爲作替罪羔羊......
因此,代理模式的出現多是這個對象不方便幹一個事情,或者不肯意幹,這個時候便會出現中間人了。
好比,我如今是一個博主,我想看我博客的人都點擊一下推薦,推薦按鈕即是真實對象,如今可能各位不肯意點,而只想看看就走,這個時候若是文檔document做爲代理者的話,若是用戶點擊了body部分,便會偷偷的將推薦點了,這即是一種神不知鬼不覺的代理。
這裏有三個角色:用戶,推薦按鈕,body,因爲用戶只是觸發了click事件,這裏直接以全局點擊事件代替。
1 $('#up').on('click', function() { 2 console.log('推薦'); 3 }) 4 $('body').on('mousemove', function () { 5 $('#up').click(); 6 })
推薦的工做原本是由up按鈕對象點擊觸發的,可是這裏卻委託了body對象執行;以$.proxy而言,其意義就是裏面乾的事情所有是代理者(this)乾的
再換個說法,若是咱們如今有一個按鈕組,咱們爲每個按鈕註冊事件彷佛有點吃虧了,因而便將實際的執行邏輯交給其父標籤
1 var parent = $('#parent'); 2 for(var i = 0; i < 10; i++){ 3 parent.append($('<input type="button" value="按鈕_' + i +'" >')); 4 } 5 function itemFn () { 6 console.log(this.val()); 7 } 8 parent.on('click', function(e) { 9 var el = $(e.target); 10 itemFn.call(el); 11 });
父元素代理了子元素的點擊事件,可是子元素回調中的this依舊是點擊元素,這個即是代理。
觀察者是前端最爲經典的模式,又稱發佈訂閱模式,他定義一個一對多的關係,讓多個觀察者同時監聽某一個主題對象,這個主題對象狀態改變時,或者觸發了某一動做,便會通知全部被觀察者做出改變更做以更新自身。
累了,這裏開始盜百度百科圖了:
如所示,主題對象會提供一個相似on接口用以添加觀察者,也會給予一個相似off接口移除觀察者,適用範圍能夠是不一樣對象間,也能夠是自身,好比model改變了會通知全部監聽model的view作改變。
1 var Model = _.inherit({ 2 initialize: function (opts) { 3 this.title = '標題'; 4 this.message = '消息'; 5 this.observes = []; 6 _.extend(this, opts); 7 }, 8 on: function(view) { 9 this.observes.push(view); 10 }, 11 off: function() { 12 //略...... 13 }, 14 //overrid 15 getviewmodel: function () { 16 return { title: this.title, message: this.message }; 17 }, 18 notify: function () { 19 for(var i = 0, len = this.observes.length; i < len; i++) { 20 this.observes[i].update(this.getviewmodel()); 21 } 22 }, 23 update: function(title, msg){ 24 this.title = title; 25 this.message = msg; 26 this.notify(); 27 } 28 }); 29 30 var View = _.inherit({ 31 initialize: function (opts) { 32 this.template = ''; 33 this.data = {}; 34 this.wrapper = $('body'); 35 this.$root = $('<div style="display: none;"></div>'); 36 _.extend(this, opts); 37 }, 38 show: function () { 39 this.$root.html(this.render(this.data)); 40 this.wrapper.append(this.$root); 41 this.$root.show(); 42 }, 43 render: function (data) { 44 return _.template(this.template)(data); 45 }, 46 update: function(data) { 47 this.$root.html(this.render(data)); 48 } 49 }); 50 51 var model = new Model(); 52 53 var v1 = new View({ 54 template: '<div><%=title%></div><div><%=message%></div>', 55 data: model.getviewmodel() 56 }); 57 58 var v2 = new View({ 59 template: '<input value="<%=title%>"><input value="<%=message%>">', 60 data: model.getviewmodel() 61 }); 62 63 model.on(v1); 64 model.on(v2); 65 66 v1.show(); 67 v2.show(); 68 69 setTimeout(function () { 70 model.update('1111', '2222'); 71 }, 3000);
這裏view首次實例化後,一旦model數據發生變化,兩個view會發生變化。
PS:這裏的model與view的實現很差,他們不該該主動發生關係,應該有一個viewController負責這些東西,這裏是說觀察者便很少說。
此次回顧了工做中一些與設計模式相關的內容,有不足請您指出,一些沒太用到的便暫時略過了。
微博求粉