發佈-訂閱模式又叫作觀察者模式,他定義了一種一對多的依賴關係,即當一個對象的狀態發生改變的時候,全部依賴他的對象都會獲得通知。前端
做爲一名前端開發人員,給DOM節點綁定事件但是再頻繁不過的事情。好比以下代碼vue
document.body.addEventListener('click',function () {
alert(2333);
},false);
document.body.click();//模擬點擊事件
複製代碼
這裏咱們訂閱了document.body的click事件,當body被點擊的時候,他就向訂閱者發佈這個消息,彈出2333.咱們也能夠隨意的增長和刪除訂閱者,當消息一發布,全部的訂閱者都會收到消息。node
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();//模擬點擊事件
複製代碼
值得注意的是,手動觸發事件這裏咱們直接用了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');
複製代碼
人的平常生活離不開各類人際交涉,好比你的朋友有不少,這時候你要結婚了,要以你爲發佈者,打開你的通信錄,挨個打電話通知各個訂閱者你要結婚的消息。抽象一下,實現發佈-訂閱模式須要:緩存
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('李四');
複製代碼
let yourMsg = {};
yourMsg.peopleList ={};
yourMsg.listen = function (key,fn) {
if (!this.peopleList[key]) { //若是沒有訂閱過此類消息,建立一個緩存列表
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}想知道你結婚`);
})
yourMsg.listen('unemployment',function (name) {
console.log(`${name}想知道你失業`);
})
yourMsg.triger('marrgie','張三');
yourMsg.triger('unemployment','李四');
複製代碼
var event = {
peopleList:[],
listen:function (key,fn) {
if (!this.peopleList[key]) { //若是沒有訂閱過此類消息,建立一個緩存列表
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}想知道你結婚`);
})
yourMsg.listen('unemployment',function (name) {
console.log(`${name}想知道你失業`);
})
yourMsg.trigger('marrgie','張三');
yourMsg.trigger('unemployment','李四');
複製代碼
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);
}
}
}
}
複製代碼
咱們一般所看到的都是先訂閱再發布,可是必需要遵照這種順序嗎?答案是不必定的。若是發佈者先發布一條消息,可是此時尚未訂閱者訂閱此消息,咱們能夠不讓此消息消失於宇宙之中。就如同QQ離線消息同樣,離線的消息被保存在服務器中,接收人下次登陸以後,纔會收到此消息。一樣的,咱們能夠創建一個存放離線事件的堆棧,當事件發佈的時候,若是此時尚未訂閱者訂閱這個事件,咱們暫時把發佈事件的動做包裹在一個函數裏,這些包裝函數會被存入堆棧中,等到有對象來訂閱事件的時候,咱們將遍歷堆棧並依次執行這些包裝函數,即重發裏面的事件,不過離線事件的生命週期只有一次,就像qq未讀消息只會提示你一次同樣。bash
由於JavaScript有回調函數這個優點存在,咱們寫開發-訂閱顯得更簡單一點。傳統的發佈-訂閱好比Java一般會把訂閱者自身當成引用傳入發佈者對象中,同時訂閱者對象還需提供一個名爲諸如update的方法,供發佈者對象在合適的時候調用。下面代碼用js模擬下傳統的實現。服務器
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();
複製代碼