发布-订阅模式又叫做观察者模式,他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知。
作为一名前端开发人员,给DOM节点绑定事件可是再频繁不过的事情。比如如下代码
document.body.addEventListener('click',function () {
alert(2333);
},false);
document.body.click();//模拟点击事件
这里我们订阅了document.body的click事件,当body被点击的时候,他就向订阅者发布这个消息,弹出2333.我们也可以随意的增加和删除订阅者,当消息一发布,所有的订阅者都会收到消息。
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未读消息只会提示你一次一样。
因为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();