談談工作中的設計模式

範大腳腳發表於2017-11-16
前言
記得剛畢業的時候參加了一次校招面試,之前表現的很好,最後時面試官問我懂不懂設計模式,我說不懂,然後就進去了;後面又參加了某大公司的校招,開始表現還行,後面面試官問我懂不懂設計模式,我說懂(上次後補習了下),最後把工廠模式的程式碼背寫到了紙上,然後就沒有然後了……
現在回想起來當時有點傻有點天真,沒有幾十萬的程式碼量,沒有一定的經驗總結,居然敢說懂設計模式,這不是找抽麼?
經過這幾年工作學習,感覺是時候系統的回憶下平時工作內容,看用到了什麼設計模式,權當總結。
小釵對設計模式的理解程度有限,文中不足之處請您拍磚。
物件導向的實現
設計模式便是物件導向的深入,物件導向的應用,所以類的實現是第一步:
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*functions*(([^()]*?))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日”這類字串轉換為日期物件,或者逆向轉換
……
工廠模式(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負責這些東西,這裡是說觀察者便不多說。
結語
這次回顧了工作中一些與設計模式相關的內容,有不足請您指出,一些沒太用到的便暫時略過了。
本文轉自葉小釵部落格園部落格,原文連結:http://www.cnblogs.com/yexiaochai/p/4292830.html,如需轉載請自行聯絡原作者


相關文章