談談工作中常用的設計模式

今天跟大家聊聊開發過程中用到的幾種設計模式。包括在校招,社招面試的時候都要問到的一些設計模式。

先看文章,如果還是不怎麼理解跟明白的話,你可以回頭看看這個視頻講解:設計模式詳解

 

面向對象的實現

設計模式便是面向對象的深入,面向對象的應用,所以類的實現是第一步:

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 = 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"

單例模式(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();//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日”這類字符串轉換爲日期對象,或者逆向轉換

......

C/C++Linux服務器開發精彩內容包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒體,P2P,Linux內核,Docker,TCP/IP,協程,DPDK多個高級知識點分享。

視頻獲取+qun:學習資料

 

工廠模式(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

③ 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 }

如之前寫的坦克大戰,創建各自坦克工廠模式也是絕佳的選擇,工廠模式暫時到此。

橋接模式(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     //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不可分割,而動畫功能這樣處理卻比較合適。

裝飾者模式(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:113
56  在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 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,這裏不對請您拍磚。

適配器模式(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:110
34  顯示loading組件 01.html:113
35  初始化情感化組件 01.html:118
36  顯示情感化組件 
37  */

代理模式(proxy)

代理模式便是幫別人做事,爲其他對象提供一種代理,以控制對這個對象的訪問,最經典的用法便是:

$.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依舊是點擊元素,這個便是代理。

觀察者模式(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     //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負責這些東西,這裏是說觀察者便不多說。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章