1 namespace ob { 2 /** 3 * 主題集合對象,發起通知器 4 */ 5 export interface ISubject { 6 7 /** 8 * 添加一個觀察者通知 9 * @param notic 通知主題 10 * @param listener 通知監聽回調函數 11 * @param thisObj 通知回調函數的this對象 12 */ 13 on(notic:string|number, listener:Function, thisObj:Object); 14 15 /** 16 * 添加一次性觀察者通知 17 * @param notic 通知主題 18 * @param listener 通知監聽回調函數 19 * @param thisObj 通知回調函數的this對象 20 */ 21 once(notic:string|number, listener:Function, thisObj:Object); 22 23 /** 24 * 發起一個通知 25 * @param notic 通知主題 26 * @param args 攜帶的參數 27 */ 28 send(notic:string|number, ...args:any[]):void; 29 30 /** 31 * 刪除一個觀察者通知 32 * @param notic 通知主題 33 * @param listener 通知監聽回調函數 34 */ 35 off(notic:string|number, listener:Function,thisObj:Object); 36 } 37 }
2.具體實現主題(Subject)html
1 namespace ob { 2 /** 3 * 主題集合對象,發起通知器,代替派發事件 4 */ 5 export class Subject implements ISubject{ 6 7 private funcMap:HashMap<string|number, NoticeList>;//根據主題存放監聽事件對象 8 9 constructor(){ 10 this.funcMap = new HashMap<string|number, NoticeList>(); 11 } 12 13 /** 14 * 添加一個觀察者通知 15 * @param notic 通知主題 16 * @param listener 通知監聽回調函數 17 * @param thisObj 通知回調函數的this對象 18 */ 19 on(notice:string|number, listener:Function, thisObj:Object):void{ 20 this.addNoticeData(notice, listener, thisObj); 21 } 22 23 /** 24 * 添加一次性觀察者通知 25 * @param notic 通知主題 26 * @param listener 通知監聽回調函數 27 * @param thisObj 通知回調函數的this對象 28 */ 29 once(notice:string|number, listener:Function, thisObj:Object):void{ 30 this.addNoticeData(notice, listener, thisObj, true); 31 } 32 33 /** 34 * 刪除一個觀察者通知 35 * @param notic 通知主題 36 * @param listener 通知監聽回調函數 37 */ 38 off(notice:string|number, listener:Function,thisObj:Object):void{ 39 let listObj = this.funcMap.get(notice); 40 if(listObj){ 41 let dataList = listObj.dataList; 42 //是否正在遍歷,若是是,則進行復制 43 if(listObj.isRunning){ 44 listObj.dataList = dataList = dataList.concat(); 45 } 46 let data:NoticeData; 47 for(let i=0,len=dataList.length;i<len;i++){ 48 data = dataList[i]; 49 if(data.listener==listener && data.thisObj==thisObj){ 50 dataList.splice(i,1);//在註冊的時候已經保證不會有重複的,因此找到一個就刪除返回 51 return; 52 } 53 } 54 } 55 } 56 57 /** 58 * 發起一個通知 59 * @param notic 通知主題 60 * @param args 攜帶的參數 61 */ 62 send(notice:string|number, ...args:any[]):void{ 63 let listObj = this.funcMap.get(notice); 64 if(listObj){ 65 let onceList:NoticeData[] = []; 66 listObj.isRunning = true; 67 for(let data of listObj.dataList){ 68 data.listener.apply(data.thisObj, args); 69 if(data.isOnce){ 70 onceList.push(data); 71 } 72 } 73 listObj.isRunning = false; 74 while(onceList.length){ 75 let data = onceList.pop(); 76 this.off(notice, data.listener, data.thisObj); 77 } 78 if(listObj.dataList.length<1){//移除監聽 79 this.funcMap.remove(notice); 80 } 81 } 82 } 83 84 85 /** 具體實現添加主題事件的方法 */ 86 private addNoticeData(notice:string|number, listener:Function, thisObj:Object, isOnce?:boolean):void{ 87 if(!listener || !thisObj){ 88 throw new Error("listener、thisObj均不能爲空");//拋出錯誤可阻止後面代碼運行 89 } 90 let listObj = this.funcMap.get(notice);//由事件主題獲取對應的方法集合 91 if(!listObj){ 92 //這裏也能夠考慮使用對象池 93 listObj = new NoticeList(); 94 this.funcMap.put(notice, listObj); 95 } 96 let dataList = listObj.dataList;//該主題下全部存放的事件數據 97 for(let data of dataList){//去重處理,不會有重複的事件被添加 98 //必須監聽方法和this相等才能決定是同一個監聽 99 if(data.listener==listener && data.thisObj==thisObj){ 100 return; 101 } 102 } 103 //判斷該主題事件是否正在遍歷中 104 if(listObj.isRunning){ 105 //爲了避免影響正常的遍歷,複製出一個數組來保存數據,那麼遍歷完的數組就會失去引用,從而被垃圾回收 106 listObj.dataList = dataList = dataList.concat(); 107 } 108 //開始實例化出一個事件對象 109 let obj = new NoticeData(listener, thisObj, isOnce); 110 dataList.push(obj); 111 } 112 } 113 }
在第2步中,須要用到幾個類,NoticeData(存放主題數據),NoticeList(存放數據數組和遍歷狀態),第三個HashMap是工具來的,用對象Object封裝成了經常使用的鍵值對像,具體代碼以下設計模式
NoticeData.ts數組
1 namespace ob { 2 export class NoticeData { 3 /** 通知主題 (暫時沒用到)**/ 4 notice:string | number; 5 /** 監聽函數 **/ 6 listener:Function; 7 /** 執行域對象 **/ 8 thisObj:Object; 9 /** 是否只執行一次就刪除 **/ 10 isOnce:boolean; 11 12 public constructor(listener:Function, thisObj:Object, isOnce?:boolean) { 13 this.listener = listener; 14 this.thisObj = thisObj; 15 this.isOnce = isOnce; 16 } 17 } 18 }
NoticeList.tsapp
1 namespace ob { 2 export class NoticeList { 3 4 /** 存放數據的數組*/ 5 dataList:NoticeData[]; 6 7 /** 是否正在遍歷,true爲正在遍歷 */ 8 isRunning:boolean; 9 10 public constructor() { 11 this.dataList = []; 12 } 13 } 14 }
工具類 HashMap.tsdom
1 /** 2 * 用Object保存鍵值對,因此K應爲簡單數據類型,好比number,string,其餘的類型Object會自動將其轉化爲string類型 3 */ 4 class HashMap<K,V> { 5 6 private obj:Object;//存放數據的對象 7 private length:number; 8 9 public constructor() { 10 this.obj = {}; 11 this.length = 0; 12 } 13 14 /** 15 * 將指定的值與此映射中的指定鍵相關聯. 16 * @param key 與指定值相關聯的鍵.key的類型應爲number|string,其餘類型會被沖掉 17 * @param value 與指定鍵相關聯的值. 18 */ 19 put(key:K, value:V):void{ 20 let obj = this.obj; 21 if(obj[key as any]){//該key不存在對應的值時,長度+1 22 this.length++; 23 } 24 obj[key as any] = value; 25 } 26 27 /** 28 * 返回此映射中映射到指定鍵的值. 29 * @param key 與指定值相關聯的鍵. 30 * @return 此映射中映射到指定值的鍵,若是此映射不包含該鍵的映射關係,則返回 undefined (或者 null). 31 */ 32 get(key:K):V{ 33 return this.obj[key as any]; 34 } 35 36 /** 37 * 獲取此映射的長度 38 */ 39 size():number{ 40 return this.length; 41 } 42 43 /** 44 * 是否爲空 45 */ 46 isEmpty():boolean{ 47 return this.length<1; 48 } 49 50 /** 51 * 若是此映射包含指定鍵的映射關係,則返回 true. 52 * @param key 測試在此映射中是否存在的鍵. 53 * @return 若是此映射包含指定鍵的映射關係,則返回 true. 54 */ 55 hasKey(key:K):boolean{ 56 if(this.obj[key as any]){ 57 return true; 58 } 59 return false; 60 } 61 62 /** 63 * 返回此映射中包含的全部key值. 64 * @return 包含全部key的數組 65 */ 66 keys():K[]{ 67 let arr:K[] = []; 68 if(this.length>0){ 69 let obj = this.obj; 70 for(let key in obj){ 71 arr.push(key as any); 72 } 73 } 74 return arr; 75 } 76 77 /** 78 * 返回此映射中包含的全部value值. 79 * @return 包含全部value的數組 80 */ 81 values():V[]{ 82 let arr:V[] = []; 83 if(this.length>0){ 84 let obj = this.obj; 85 for(let key in obj){ 86 arr.push(obj[key]); 87 } 88 } 89 return arr; 90 } 91 92 /** 93 * 遍歷全部映射均執行方法,方法攜帶的參數只包括映射的值 94 */ 95 forEach(func:Function, thisObj:Object):void{ 96 let obj = this.obj; 97 for(let key in obj){ 98 func.call(thisObj, obj[key]); 99 } 100 } 101 102 /** 103 * 遍歷全部映射均執行方法,方法攜帶的參數包括映射的key, value 104 */ 105 forKeyValue(func:Function, thisObj):void{ 106 let obj = this.obj; 107 for(let key in obj){ 108 func.call(thisObj, key, obj[key]); 109 } 110 } 111 112 /** 113 * 刪除並返回此映射中映射到指定鍵的值. 114 * @param key 與指定值相關聯的鍵. 115 * @return 返回此映射中映射到指定值的鍵,若是此映射不包含該鍵的映射關係,則返回 undefined (或者 null). 116 */ 117 remove(key:K):V{ 118 let value = this.obj[key as any]; 119 if(value){ 120 delete this.obj[key as any]; 121 this.length--; 122 } 123 return value; 124 } 125 126 /** 127 * 清除全部映射 128 */ 129 clear(){ 130 this.length = 0; 131 let obj = this.obj; 132 for(let key in obj){ 133 delete obj[key]; 134 } 135 } 136 }
在項目中,將Subject進行了二次封裝,相似於單例,你們共用一個對象函數
下面是一個簡單的示例:工具
1 class Test extends egret.DisplayObjectContainer { 2 public constructor() { 3 super(); 4 this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this); 5 } 6 7 private onAddToStage(e: egret.Event): void { 8 let txt1 = new egret.TextField(); 9 txt1.size = 50; 10 txt1.text = "這裏是一次性事件變化的顯示內容"; 11 this.addChild(txt1); 12 13 let txt2 = new egret.TextField(); 14 txt2.size = 50; 15 txt2.text = "這裏是通用事件變化的顯示區域"; 16 txt2.y = 100; 17 this.addChild(txt2); 18 19 let subject = new ob.Subject(); 20 subject.once("123", (p1,p2)=>{ 21 console.log("p1="+p1," p2="+p2); 22 txt1.textColor = 0xfff000; 23 },this); 24 25 let cout = 0; 26 subject.on("123", testFun, this); 27 function testFun(p1,p2){ 28 console.log("p1="+p1," p2="+p2); 29 txt2.textColor = Math.random() * 0xffffff; 30 cout++; 31 if(cout>10){ 32 subject.off("123", testFun, this); 33 } 34 } 35 36 this.stage.addEventListener(egret.TouchEvent.TOUCH_TAP, (e:egret.TouchEvent)=>{ 37 console.log("點了面板") 38 subject.send("123","參數1","參數2"); 39 }, this); 40 41 } 42 43 }
總結:在項目中,相似這種模式代替了事件機制,裏面的邏輯部分就在Subject類中,外面調用時只需聲明一個對象,供全部模塊調用便可測試