Javascript設計模式之發(fā)布訂閱模式
簡介
發(fā)布-訂閱模式又叫做觀察者模式,他定義了一種一對多的依賴關系,即當一個對象的狀態(tài)發(fā)生改變的時候,所有依賴他的對象都會得到通知。
回憶曾經(jīng)
作為一名前端開發(fā)人員,給DOM節(jié)點綁定事件可是再頻繁不過的事情。比如如下代碼
document.body.addEventListener('click',function () { alert(2333); },false); document.body.click();//模擬點擊事件
這里我們訂閱了document.body的click事件,當body被點擊的時候,他就向訂閱者發(fā)布這個消息,彈出2333.我們也可以隨意的增加和刪除訂閱者,當消息一發(fā)布,所有的訂閱者都會收到消息。
document.body.addEventListener('click',function () { alert(11111); },false); document.body.addEventListener('click',function () { alert(222); },false); document.body.addEventListener('click',function () { alert(333); },false); document.body.click();//模擬點擊事件
值得注意的是,手動觸發(fā)事件這里我們直接用了document.body.click();但是更好的做法是IE下用fireEvent,標準瀏覽器下用dispatchEvent,如下:
let fireEvent = function (element,event) { if (document.createEventObject) { var evt = document.createEventObject(); return element.fireEvent('on'+event,evt); }else{ var evt = document.createEvent('HTMLEvents'); evt.initEvent(event,true,true); return element.dispatchEvent(evt); } } document.addEventListener('shout',function (event) { alert('shout'); }) fireEvent(document,'shout');
暢談現(xiàn)在
人的日常生活離不開各種人際交涉,比如你的朋友有很多,這時候你要結(jié)婚了,要以你為發(fā)布者,打開你的通訊錄,挨個打電話通知各個訂閱者你要結(jié)婚的消息。抽象一下,實現(xiàn)發(fā)布-訂閱模式需要:
- 發(fā)布者(你)
- 緩存列表(通訊錄,你的朋友們相當于訂閱了你的所有消息)
- 發(fā)布消息的時候遍歷緩存列表,依次觸發(fā)里面存放的訂閱者的回調(diào)函數(shù)(挨個打電話)
- 另外,回調(diào)函數(shù)中還可以添加很多參數(shù),,訂閱者可以接收這些參數(shù),比如你會告訴他們婚禮時間,地點等,訂閱者收到消息后可以進行各自的處理。
let yourMsg = {}; yourMsg.peopleList = []; yourMsg.listen = function (fn) { this.peopleList.push(fn); } yourMsg.triger = function () { for(var i = 0,fn;fn=this.peopleList[i++];){ fn.apply(this,arguments); } } yourMsg.listen(function (name) { console.log(`${name}收到了你的消息`); }) yourMsg.listen(function (name) { console.log('哈哈'); }) yourMsg.triger('張三'); yourMsg.triger('李四');
以上就是一個簡單的發(fā)布-訂閱的實現(xiàn),但是我們會發(fā)現(xiàn)訂閱者會收到發(fā)布者發(fā)布的每一條信息,如果李四比較陰暗,不想聽到你結(jié)婚的消息,只想聽到你的壞消息,比如你被開除了,他就心里高興。這時候我們就需要加一個key,讓訂閱者只訂閱自己感興趣的消息。
let yourMsg = {}; yourMsg.peopleList ={}; yourMsg.listen = function (key,fn) { if (!this.peopleList[key]) { //如果沒有訂閱過此類消息,創(chuàng)建一個緩存列表 this.peopleList[key] = []; } this.peopleList[key].push(fn); } yourMsg.triger = function () { let key = Array.prototype.shift.call(arguments); let fns = this.peopleList[key]; if (!fns || fns.length == 0) {//沒有訂閱 則返回 return false; } for(var i=0,fn;fn=fns[i++];){ fn.apply(this,arguments); } } yourMsg.listen('marrgie',function (name) { console.log(`${name}想知道你結(jié)婚`); }) yourMsg.listen('unemployment',function (name) { console.log(`${name}想知道你失業(yè)`); }) yourMsg.triger('marrgie','張三'); yourMsg.triger('unemployment','李四');
你需要發(fā)布消息,同樣的所有的人都有朋友圈,也都需要發(fā)布消息,因此我們有必要把發(fā)布-訂閱的功能提取出來,放在一個單獨的對象內(nèi),誰需要誰去動態(tài)安裝發(fā)布-訂閱功能(installEvent函數(shù)實現(xiàn)了動態(tài)安裝發(fā)布-訂閱功能)。參考前端手寫面試題詳細解答
var event = { peopleList:[], listen:function (key,fn) { if (!this.peopleList[key]) { //如果沒有訂閱過此類消息,創(chuàng)建一個緩存列表 this.peopleList[key] = []; } this.peopleList[key].push(fn) }, trigger:function () { let key = Array.prototype.shift.call(arguments); let fns = this.peopleList[key]; if (!fns || fns.length == 0) {//沒有訂閱 則返回 return false; } for(var i=0,fn;fn=fns[i++];){ fn.apply(this,arguments); } } } var installEvent = function (obj) { for(var i in event){ obj[i] = event[i]; } } let yourMsg = {}; installEvent(yourMsg); yourMsg.listen('marrgie',function (name) { console.log(`${name}想知道你結(jié)婚`); }) yourMsg.listen('unemployment',function (name) { console.log(`${name}想知道你失業(yè)`); }) yourMsg.trigger('marrgie','張三'); yourMsg.trigger('unemployment','李四');
有時間我們需要取消訂閱的事件,比如李四是你的好朋友,但是因為一件事情,你倆鬧掰了,你把他從你的通訊錄中給刪除掉了,這里我們給event增加一個remove方法;
remove:function (key,fn) { var fns = this.clientList[key]; if(!fns){ return false; } if(!fn){ fns && (fns.length=0) }else{ for (let index = 0; index < fns.length; index++) { const _fn = fns[index]; if(_fn === fn){ fns.splice(index,1); } } } }
發(fā)布-訂閱的順序探討
我們通常所看到的都是先訂閱再發(fā)布,但是必須要遵守這種順序嗎?答案是不一定的。如果發(fā)布者先發(fā)布一條消息,但是此時還沒有訂閱者訂閱此消息,我們可以不讓此消息消失于宇宙之中。就如同QQ離線消息一樣,離線的消息被保存在服務器中,接收人下次登錄之后,才會收到此消息。同樣的,我們可以建立一個存放離線事件的堆棧,當事件發(fā)布的時候,如果此時還沒有訂閱者訂閱這個事件,我們暫時把發(fā)布事件的動作包裹在一個函數(shù)里,這些包裝函數(shù)會被存入堆棧中,等到有對象來訂閱事件的時候,我們將遍歷堆棧并依次執(zhí)行這些包裝函數(shù),即重發(fā)里面的事件,不過離線事件的生命周期只有一次,就像qq未讀消息只會提示你一次一樣。
JavaScript實現(xiàn)發(fā)布-訂閱模式的便利性
因為JavaScript有回調(diào)函數(shù)這個優(yōu)勢存在,我們寫開發(fā)-訂閱顯得更簡單一點。傳統(tǒng)的發(fā)布-訂閱比如Java通常會把訂閱者自身當成引用傳入發(fā)布者對象中,同時訂閱者對象還需提供一個名為諸如update的方法,供發(fā)布者對象在合適的時候調(diào)用。下面代碼用js模擬下傳統(tǒng)的實現(xiàn)。
function Dep() { this.subs = []; } Dep.prototype.addSub = function (sub) { this.subs.push(sub); } Dep.prototype.notify = function () { this.subs.forEach(sub=>sub.update()); } function Watcher(fn) { this.fn = fn; } Watcher.prototype.update = function () { this.fn(); } var dep = new Dep(); dep.addSub(new Watcher(function () { console.log('okokok'); })) dep.notify();
小結(jié)
- 發(fā)布-訂閱的優(yōu)勢很明顯,做到了時間上的解耦和對象之間的解耦,從架構(gòu)上看,MVC,MVVM都少不了發(fā)布-訂閱的參與,我們常用的Vue也是基于發(fā)布-訂閱的,最近會抽時間寫下vue的源碼實現(xiàn),同樣的node中的EventEmitter也是發(fā)布訂閱的,之前也手寫過它的實現(xiàn)。
- 發(fā)布-訂閱同時也是有缺點存在的,創(chuàng)建訂閱者本身要消耗一定的時間和內(nèi)存,而且當你訂閱一個消息以后,可能此消息最后都未發(fā)生,但是這個訂閱者會始終存在于內(nèi)存中。如果程序中大量使用發(fā)布-訂閱的話,也會使得程序跟蹤bug變得困難。
到此這篇關于Javascript設計模式之發(fā)布訂閱模式的文章就介紹到這了,更多相關JS發(fā)布訂閱模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
js操作XML文件的實現(xiàn)方法兼容IE與FireFox
下面小編就為大家?guī)硪黄猨s操作XML文件的實現(xiàn)方法兼容IE與FireFox。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-06-06Bootstrap整體框架之JavaScript插件架構(gòu)
這篇文章主要介紹了Bootstrap整體框架之JavaScript插件架構(gòu)的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12bootstrap3-dialog-master模態(tài)框使用詳解
這篇文章主要為大家詳細介紹了bootstrap3-dialog-master模態(tài)框的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08