發佈/訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關係。當一個對象的狀態(發佈者)發生改變時,全部依賴於它的對象都將獲得通知。在 JavaScript 開發中,咱們通常用事件模型來替代傳統的發佈—訂閱模式。編程
觀察者模式應該是最經常使用的模式之一。在不少語言裏都獲得大量應用。包括咱們平時接觸的dom事件,也是js和dom之間實現的一種觀察者模式。設計模式
document.body.addEventListener('click',function () { alert(111); },false); document.body.addEventListener('click',function () { alert(222); },false); document.body.addEventListener('click',function () { alert(333); },false); document.body.click();//模擬點擊事件
這裏咱們訂閱了document.body 的 click 事件,當 body 被點擊的時候,他就向訂閱者發佈這個消息,順序彈出111 222 333。咱們也能夠隨意的增長和刪除訂閱者。總之,當消息一發布,全部的訂閱者都會收到消息。數組
這裏,手動觸發事件直接用了document.body.click();可是更好的作法是IE下用 fireEvent,標準瀏覽器下用 dispatchEvent。瀏覽器
發佈/訂閱模式能夠用一個全局的 Event 對象來實現,訂閱者不須要了解消息來自哪一個發佈者,發佈者也不知道消息會推送給哪些訂閱者,Event 做爲一個相似「中介者」 的角色,把訂閱者和發佈者聯繫起來。架構
let Event = (function () { let clientList = {}; let listen; let trigger; let remove; listen = function (name, cb) { if( !clientList[name] ) { clientList[name] = []; } clientList[name].push(cb); }; trigger = function () { let name = [].shift.call(arguments); let fns = clientList[name]; if( !fns || fns.length == 0 ) return false; fns.map((item, index) => { item(...arguments); // 不要直接將arguments傳入 }) }; remove = function (name, cb) { let fns = clientList[name]; if( !fns ) return false; // name對應的消息麼有被人訂閱 if( !cb ) { // 沒有傳入cb(具體的回調函數), 表示取消 name 對應的全部訂閱 fns && (fns.length = 0); // 不加括號會報錯:ReferenceError: Invalid left-hand side in assignment } else { clientList[name] = fns.filter((item, index) => { return item !== cb; // filter不改變原數組 return不忘記 }) /* // 反向遍歷 for(let i = fns.length - 1; i >= 0; i--) { let _cb = fns[i]; if(_cb === cb) { fns.splice(i, 1); } } */ } }; return { listen: listen, trigger: trigger, remove: remove } })(); let foo1 = function( num ){ console.log('foo triggered'); } let foo2 = function( num ){ console.log(num); } Event.listen('foo', foo1); Event.listen('foo', foo2); Event.trigger('foo', 2); // foo triggered // 2
看下下面這段代碼的輸出,能夠看到是沒法取消對應的訂閱信息的。就像{} === {}
結果是 false同樣(引用類型的特色:按地址存放)。dom
Event.remove('foo', function(num){console.log(num)}); Event.trigger('foo', 2); // let obj1 = {}; // let obj2 = {}; //obj1 === obj2 // false //but //obj1 === obj1 // true
正確寫法:異步
Event.remove('foo', foo2); Event.trigger('foo', 2);
咱們所瞭解的發佈/訂閱模式,都是訂閱者必須先訂閱一個消息,隨後才能接收到發佈者發佈的消息。若是把順序反過來,發佈者先發佈一個消息,而在此以前並無對象來訂閱它,那麼這條消息就消失在宇宙中了。ide
創建一個存放離線事件的堆棧,當事件發佈的時候,若是此時尚未訂閱者來訂閱這個事件,咱們暫時把發佈事件的動做包裹在一個函數裏,這些包裝函數將被存入堆棧中,等到終於有對象來訂閱此事件的時候,咱們將遍歷堆棧而且依次執行這些包裝函數,也就是從新發布里面的事件。固然離線事件的生命週期只有一次,就像QQ的未讀消息只會被重 新閱讀一次,因此剛纔的操做咱們只能進行一次。異步編程
發佈—訂閱模式的優勢很是明顯:函數
它的應用很是普遍,既能夠用在異步編程中,也能夠幫助咱們完成更鬆耦合的代碼編寫。發佈/訂閱模式還能夠用來幫助實現一些別的設計模式,好比中介者模式。
從架構上來看,不管是 MVC 仍是 MVVM,,都少不了發佈/訂閱模式的參與,並且 JavaScript 自己也是一門基於事件驅動的語言。
固然,發佈/訂閱模式也不是徹底沒有缺點。建立訂閱者自己要消耗必定的時間和內存,並且當你訂閱一個消息後,也許此消息最後都未發生,但這個訂閱者會始終存在於內存中。
另外,發佈/訂閱模式雖然能夠弱化對象之間的聯繫,但若是過分使用的話,對象和對象之間的必要聯繫也將被深埋在背後,會致使程序難以跟蹤維護和理解。特別是有多個發佈者和訂閱者嵌套到一塊兒的時候,要跟蹤一個 bug 不是件輕鬆的事情。