今天跟大家聊聊開發過程中用到的幾種設計模式。包括在校招,社招面試的時候都要問到的一些設計模式。
面向物件的實現設計模式便是面向物件的深入,面向物件的應用,所以類的實現是第一步:
PS:這裡依賴了underscore,各位自己加上吧。
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 };
使用測試:
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 = name18 }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"
單例模式(Singleton)
單列為了保證一個類只有一個例項,如果不存在便直接返回,如果存在便返回上一次的例項,其目的一般是為了資源最佳化。
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();//119 var m2 = new UIAlert('2');20 m2.showMessage();//221 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 this21 }22 }23 24 var m1 = UIAlert.getInstance();25 m1.setMsg(1);26 m1.showMessage();//127 var m2 = UIAlert.getInstance();28 m2.setMsg(2);29 m2.showMessage();//230 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日”這類字串轉換為日期物件,或者逆向轉換
......
工廠模式(Factory)工廠模式是一個比較常用的模式,介於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
所以我們的程式碼可以是這樣的:
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 }
如之前寫的坦克大戰,建立各自坦克工廠模式也是絕佳的選擇,工廠模式暫時到此。
橋接模式(bridge)橋接模式一個非常典型的使用便是在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 //override23 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不可分割,而動畫功能這樣處理卻比較合適。
裝飾者模式(decorator)裝飾者模式的意圖是為一個物件動態的增加一些額外職責;是類繼承的另外一種選擇,一個是編譯時候增加行為,一個是執行時候。
裝飾者要求其實現與包裝的物件統一,並做到過程透明,意味著可以用他來包裝其他物件,而使用方法與原來一致。
一次邏輯的執行可以包含多個裝飾物件,這裡舉個例子來說,在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();
說實話,就站在前端的角度,以及我的視野來說,這個裝飾者其實不太實用,換個說法,這個裝飾者模式非常類似面向切口程式設計,就是在某一個點前做點事情,後做點事情,這個時候事件管道似乎更加合適。
組合模式(composite)組合模式是前端比較常用的一個模式,目的是解耦複雜程式的內部結構,更業務一點便是將一個複雜元件分成多個小元件,最後保持使用時單個物件和組合物件具有一致性。
假如我這裡有一個彈出層容器元件,其內部會有三個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:11356 在select容器中,顯示元件:select元件57 在select容器中,顯示元件:select元件58 */
怎麼說呢,真實的使用場景肯定會有所不同,我們不會在容器外例項化select元件,而是直接在其內部完成;組合模式在工作中是比較常用的,而且容器元件未必會有add,remove等實現,往往只是要求你初始化時能將其內部元件顯示好就行。
門面模式(facade)門面模式又稱為外觀模式,旨在為子系統提供一致的介面,門面模式提供一個高層的介面,這個介面使得子系統更加容易使用;如果沒有外觀模式使用者便會直接呼叫子系統,那麼使用者必須知道子系統更多的細節,而可能造成麻煩與不便。
我對該模式比較印象深刻是由於一次框架的誤用,當時做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 scheme32 // 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=hybrid1233415 var _getHybridUrl = function (params) {16 var url = '';17 //...aa操作paramss生成url18 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,這裡不對請您拍磚。
介面卡模式(adapter)介面卡模式的目的是將一類介面轉換為使用者希望的另外一種介面,使原本不相容的介面可以一起工作。
事實上這種模式一旦使用可能就面臨第三方或者其它模組要與你的模組一起使用的需求發生了,這個在.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:11034 顯示loading元件 01.html:11335 初始化情感化元件 01.html:11836 顯示情感化元件 37 */
代理模式(proxy)代理模式便是幫別人做事,為其他物件提供一種代理,以控制對這個物件的訪問,最經典的用法便是:
$.proxy(function() {}, this);
可以看到,最終做的人,依舊是自己,只不過別人以為是代理物件做的,這個有點類似於為人作嫁衣;當然,也可以理解為做替罪羔羊......
所以,代理模式的出現可能是這個物件不方便幹一個事情,或者不願意幹,這個時候便會出現中間人了。
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依舊是點選元素,這個便是代理。
觀察者模式(observer)觀察者是前端最為經典的模式,又稱釋出訂閱模式,他定義一個一對多的關係,讓多個觀察者同時監聽某一個主題物件,這個主題物件狀態改變時,或者觸發了某一動作,便會通知所有被觀察者作出改變動作以更新自身。
累了,這裡開始盜百度百科圖了:
如所示,主題物件會提供一個類似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 //overrid15 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負責這些東西,這裡是說觀察者便不多說。