主頁 > 軟體設計 > 談談作業中常用的設計模式

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

2021-01-31 10:30:01 軟體設計

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

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

面向物件的實作

設計模式便是面向物件的深入,面向物件的應用,所以類的實作是第一步:

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負責這些東西,這里是說觀察者便不多說,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/254780.html

標籤:其他

上一篇:Linux網路編程必學的TCP/IP協議——圖解分層(通俗易懂)【建議新手收藏】

下一篇:一篇帶你掌握TCP連接管理——三次握手

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more