Javascript設(shè)計(jì)模式之發(fā)布訂閱模式
簡(jiǎn)介
發(fā)布-訂閱模式又叫做觀察者模式,他定義了一種一對(duì)多的依賴關(guān)系,即當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變的時(shí)候,所有依賴他的對(duì)象都會(huì)得到通知。
回憶曾經(jīng)
作為一名前端開(kāi)發(fā)人員,給DOM節(jié)點(diǎn)綁定事件可是再頻繁不過(guò)的事情。比如如下代碼
document.body.addEventListener('click',function () {
alert(2333);
},false);
document.body.click();//模擬點(diǎn)擊事件這里我們訂閱了document.body的click事件,當(dāng)body被點(diǎn)擊的時(shí)候,他就向訂閱者發(fā)布這個(gè)消息,彈出2333.我們也可以隨意的增加和刪除訂閱者,當(dāng)消息一發(fā)布,所有的訂閱者都會(huì)收到消息。
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();//模擬點(diǎn)擊事件值得注意的是,手動(dòng)觸發(fā)事件這里我們直接用了document.body.click();但是更好的做法是IE下用fireEvent,標(biāo)準(zhǔn)瀏覽器下用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)在
人的日常生活離不開(kāi)各種人際交涉,比如你的朋友有很多,這時(shí)候你要結(jié)婚了,要以你為發(fā)布者,打開(kāi)你的通訊錄,挨個(gè)打電話通知各個(gè)訂閱者你要結(jié)婚的消息。抽象一下,實(shí)現(xiàn)發(fā)布-訂閱模式需要:
- 發(fā)布者(你)
- 緩存列表(通訊錄,你的朋友們相當(dāng)于訂閱了你的所有消息)
- 發(fā)布消息的時(shí)候遍歷緩存列表,依次觸發(fā)里面存放的訂閱者的回調(diào)函數(shù)(挨個(gè)打電話)
- 另外,回調(diào)函數(shù)中還可以添加很多參數(shù),,訂閱者可以接收這些參數(shù),比如你會(huì)告訴他們婚禮時(shí)間,地點(diǎn)等,訂閱者收到消息后可以進(jìn)行各自的處理。
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('李四');
以上就是一個(gè)簡(jiǎn)單的發(fā)布-訂閱的實(shí)現(xiàn),但是我們會(huì)發(fā)現(xiàn)訂閱者會(huì)收到發(fā)布者發(fā)布的每一條信息,如果李四比較陰暗,不想聽(tīng)到你結(jié)婚的消息,只想聽(tīng)到你的壞消息,比如你被開(kāi)除了,他就心里高興。這時(shí)候我們就需要加一個(gè)key,讓訂閱者只訂閱自己感興趣的消息。
let yourMsg = {};
yourMsg.peopleList ={};
yourMsg.listen = function (key,fn) {
if (!this.peopleList[key]) { //如果沒(méi)有訂閱過(guò)此類(lèi)消息,創(chuàng)建一個(gè)緩存列表
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) {//沒(méi)有訂閱 則返回
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ā)布-訂閱的功能提取出來(lái),放在一個(gè)單獨(dú)的對(duì)象內(nèi),誰(shuí)需要誰(shuí)去動(dòng)態(tài)安裝發(fā)布-訂閱功能(installEvent函數(shù)實(shí)現(xiàn)了動(dòng)態(tài)安裝發(fā)布-訂閱功能)。參考前端手寫(xiě)面試題詳細(xì)解答
var event = {
peopleList:[],
listen:function (key,fn) {
if (!this.peopleList[key]) { //如果沒(méi)有訂閱過(guò)此類(lèi)消息,創(chuàng)建一個(gè)緩存列表
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) {//沒(méi)有訂閱 則返回
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','李四');有時(shí)間我們需要取消訂閱的事件,比如李四是你的好朋友,但是因?yàn)橐患虑?,你倆鬧掰了,你把他從你的通訊錄中給刪除掉了,這里我們給event增加一個(gè)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ā)布一條消息,但是此時(shí)還沒(méi)有訂閱者訂閱此消息,我們可以不讓此消息消失于宇宙之中。就如同QQ離線消息一樣,離線的消息被保存在服務(wù)器中,接收人下次登錄之后,才會(huì)收到此消息。同樣的,我們可以建立一個(gè)存放離線事件的堆棧,當(dāng)事件發(fā)布的時(shí)候,如果此時(shí)還沒(méi)有訂閱者訂閱這個(gè)事件,我們暫時(shí)把發(fā)布事件的動(dòng)作包裹在一個(gè)函數(shù)里,這些包裝函數(shù)會(huì)被存入堆棧中,等到有對(duì)象來(lái)訂閱事件的時(shí)候,我們將遍歷堆棧并依次執(zhí)行這些包裝函數(shù),即重發(fā)里面的事件,不過(guò)離線事件的生命周期只有一次,就像qq未讀消息只會(huì)提示你一次一樣。
JavaScript實(shí)現(xiàn)發(fā)布-訂閱模式的便利性
因?yàn)镴avaScript有回調(diào)函數(shù)這個(gè)優(yōu)勢(shì)存在,我們寫(xiě)開(kāi)發(fā)-訂閱顯得更簡(jiǎn)單一點(diǎn)。傳統(tǒng)的發(fā)布-訂閱比如Java通常會(huì)把訂閱者自身當(dāng)成引用傳入發(fā)布者對(duì)象中,同時(shí)訂閱者對(duì)象還需提供一個(gè)名為諸如update的方法,供發(fā)布者對(duì)象在合適的時(shí)候調(diào)用。下面代碼用js模擬下傳統(tǒng)的實(shí)現(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)勢(shì)很明顯,做到了時(shí)間上的解耦和對(duì)象之間的解耦,從架構(gòu)上看,MVC,MVVM都少不了發(fā)布-訂閱的參與,我們常用的Vue也是基于發(fā)布-訂閱的,最近會(huì)抽時(shí)間寫(xiě)下vue的源碼實(shí)現(xiàn),同樣的node中的EventEmitter也是發(fā)布訂閱的,之前也手寫(xiě)過(guò)它的實(shí)現(xiàn)。
- 發(fā)布-訂閱同時(shí)也是有缺點(diǎn)存在的,創(chuàng)建訂閱者本身要消耗一定的時(shí)間和內(nèi)存,而且當(dāng)你訂閱一個(gè)消息以后,可能此消息最后都未發(fā)生,但是這個(gè)訂閱者會(huì)始終存在于內(nèi)存中。如果程序中大量使用發(fā)布-訂閱的話,也會(huì)使得程序跟蹤bug變得困難。
到此這篇關(guān)于Javascript設(shè)計(jì)模式之發(fā)布訂閱模式的文章就介紹到這了,更多相關(guān)JS發(fā)布訂閱模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS簡(jiǎn)單生成由字母數(shù)字組合隨機(jī)字符串示例
這篇文章主要介紹了JS簡(jiǎn)單生成由字母數(shù)字組合隨機(jī)字符串,結(jié)合實(shí)例形式分析了javascript使用Math.random()生成隨機(jī)字符串相關(guān)操作技巧,需要的朋友可以參考下2018-05-05
js寫(xiě)的方法實(shí)現(xiàn)上傳圖片之后查看大圖
用js寫(xiě)了一個(gè)方法,然后在image的onmouseover事件中調(diào)用此方法,這樣在鼠標(biāo)懸浮在小圖上面的時(shí)候,其大圖就會(huì)自動(dòng)的顯示出來(lái)2014-03-03
非常好用的JsonToString 方法 簡(jiǎn)單實(shí)例
這篇文章介紹了非常好用的JsonToString簡(jiǎn)單實(shí)例,有需要的朋友可以參考一下2013-07-07
js操作XML文件的實(shí)現(xiàn)方法兼容IE與FireFox
下面小編就為大家?guī)?lái)一篇js操作XML文件的實(shí)現(xiàn)方法兼容IE與FireFox。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06
Bootstrap整體框架之JavaScript插件架構(gòu)
這篇文章主要介紹了Bootstrap整體框架之JavaScript插件架構(gòu)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
JavaScript實(shí)現(xiàn)簡(jiǎn)單的日歷效果
本文給大家分享的是一個(gè)簡(jiǎn)單的JavaScript制作的日歷模板,小伙伴們可以根據(jù)自己的需求,繼續(xù)補(bǔ)充,希望大家能夠喜歡2016-09-09
bootstrap3-dialog-master模態(tài)框使用詳解
這篇文章主要為大家詳細(xì)介紹了bootstrap3-dialog-master模態(tài)框的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08

