這篇筆記主要記錄學習思路及收穫,分享出來拋磚引玉,若有謬誤或優化空間,歡迎交流。javascript
要理解觀察者模式,能夠類比vue中的EventBus
,其實就是一個全局的觀察者對象($bus
),上面有註冊事件($bus.on()
)和發送事件($bus.emit()
)的方法,固然由於須要會註冊不少事件,因此內部還有一個事件列表屬性_events
來存儲註冊的事件。下面爲學習筆記,對觀察者模式作簡單實現。html
基於上面的思路,首先要有一個對象,它有一個私有的列表屬性和對外暴露的兩個方法vue
let Observer = (()=>{ _events:[]; return { retister:()=>{}, issue:()=>{}, } })()
接下來一步步實現。java
首先,觀察者對象內部要有一個存儲事件的列表屬性學習
let Observer = (()=>{ // 防止事件隊列暴露而被篡改故將事件容器做爲靜態私有變量保存 let _events = []; })()
其次是註冊事件的register
方法,須要兩個參數:須要觀察的事件名稱type
和這個事件被觸發後具體的執行內容fn
:優化
let Observer = (()=>{ // 防止事件隊列暴露而被篡改故將事件容器做爲靜態私有變量保存 let _events = {}; return { register: (type,fn)=>{ //若是此消息類型不存在則應該建立一個(判斷對象上是否有某個屬性) // if (typeof(_events[type])==='undefined'){ if (!(type in _events)){ _events[type] = [fn]; } else {//若是此消息類型已存在,則直接將對應動做推入消息隊列 _events[type].push(fn); } }, } })()
再而後就是觸發事件的issue
方法,也須要兩個參數:要觸發的事件type
及傳遞過去的參數arg
this
let Observer = (()=>{ // 防止事件隊列暴露而被篡改故將事件容器做爲靜態私有變量保存 let _events = {}; return { register: (type,fn)=>{ //若是此消息類型不存在則應該建立一個(判斷對象上是否有某個屬性) // if (typeof(_events[type])==='undefined'){ if (!(type in _events)){ _events[type] = [fn]; } else {//若是此消息類型已存在,則直接將對應動做推入消息隊列 _events[type].push(fn); } }, issue: (type,arg)=>{ // 若是沒有此類型,返回 // if (typeof (_events[type]) === 'undefined'){ if (!(type in _events)) { return; } for (let i=0;i<_events[type].length;i++){ _events[type][i](arg); } }, } })()
最後,能夠再加一個能夠移除已監聽事件的remove
方法es5
let Observer = (()=>{ // 防止事件隊列暴露而被篡改故將事件容器做爲靜態私有變量保存 let _events = {}; return { register: (type,fn)=>{ //若是此消息類型不存在則應該建立一個(判斷對象上是否有某個屬性) // if (typeof(_events[type])==='undefined'){ if (!(type in _events)){ _events[type] = [fn]; } else {//若是此消息類型已存在,則直接將對應動做推入消息隊列 _events[type].push(fn); } }, issue: (type,arg)=>{ // 若是沒有此類型,返回 // if (typeof (_events[type]) === 'undefined'){ if (!(type in _events)) { return; } for (let i=0;i<_events[type].length;i++){ _events[type][i](arg); } }, // 此方法能夠類比document.removerEventListener(evt,fn) remove: (type,fn)=>{ // 若是這個類型的事件存在 if (_events[type] instanceof Array){ for (let i = 0; i < _events[type].length; i++) { _events[type][i] === fn && _events[type].splice(i,1); } } }, // 由於外部獲取不到私有屬性`_events`,因此臨時用這個方法檢查`remove`方法是否生效 check (){ console.log('_events事件隊列',_events); } } })()
至此一個基本的觀察者對象就寫出來了,接下來跑一下看效果。prototype
// 註冊的事件被觸發後須要執行的動做 function handler (val){ console.log('val:',val*2); } // 註冊事件及對應的執行動做 Observer.register('eventName',handler); // 觸發事件 function test (){ Observer.issue('eventName',5); //10 // 對比執行remove事件先後的事件列表內容 Observer.check(); Observer.remove('eventName',fn); observer.check(); }
觀察者模式在解決類的耦合中的應用小例子。想象一下課堂上老師提問,學生回答問題的場景。老師提問的問題名稱至關於發出的事件,若是學生聽到這個問題(事先註冊了這個問題名稱),那麼便會回答(響應事件的動做)。code
學生類:
let Student = function (result) { // 問題答案 this.result = result; // 回答的動做 this.answer = ()=>{ console.log('答案',this.result); } } Student.prototype.listen = function(question){ Observer.register(question,this.answer); } Student.prototype.sleep = function (question) { Observer.remove(question,this.answer); console.log('這個問題我聽不到聽不到~'); }
教師類:
let Teacher = function (){} // 對某一個問題發出問題 Teacher.prototype.ask = function (question) { Observer.issue(question); }
執行:
let student1 = new Student('學生1回答問題'); let student2 = new Student('學生2回答問題'); let student3 = new Student('學生3:呼嚕呼嚕~'); student1.listen('雞生蛋仍是蛋生雞?'); student2.listen('雞生蛋仍是蛋生雞?'); student3.listen('雞生蛋仍是蛋生雞?'); let teacher = new Teacher(); // 老師發問 function test () { teacher.ask('雞生蛋仍是蛋生雞?'); } //答案 學生1回答問題 //es5觀察者.html:68 答案 學生2回答問題 //es5觀察者.html:68 答案 學生3:呼嚕呼嚕~