简介

发布-订阅模式又叫做观察者模式,他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知。

回忆曾经

作为一名前端开发人员,给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');
复制代码

畅谈现在

人的日常生活离不开各种人际交涉,比如你的朋友有很多,这时候你要结婚了,要以你为发布者,打开你的通讯录,挨个打电话通知各个订阅者你要结婚的消息。抽象一下,实现发布-订阅模式需要:

  1. 发布者(你)
  2. 缓存列表(通讯录,你的朋友们相当于订阅了你的所有消息)
  3. 发布消息的时候遍历缓存列表,依次触发里面存放的订阅者的回调函数(挨个打电话)
  4. 另外,回调函数中还可以添加很多参数,,订阅者可以接收这些参数,比如你会告诉他们婚礼时间,地点等,订阅者收到消息后可以进行各自的处理。
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('李四');复制代码
  • 以上就是一个简单的发布-订阅的实现,但是我们会发现订阅者会收到发布者发布的每一条信息,如果李四比较阴暗,不想听到你结婚的消息,只想听到你的坏消息,比如你被开除了,他就心里高兴。这时候我们就需要加一个key,让订阅者只订阅自己感兴趣的消息。
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','李四');
复制代码
  • 你需要发布消息,同样的所有的人都有朋友圈,也都需要发布消息,因此我们有必要把发布-订阅的功能提取出来,放在一个单独的对象内,谁需要谁去动态安装发布-订阅功能(installEvent函数实现了动态安装发布-订阅功能)。
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','李四');
复制代码
  • 有时间我们需要取消订阅的事件,比如李四是你的好朋友,但是因为一件事情,你俩闹掰了,你把他从你的通讯录中给删除掉了,这里我们给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);}}}}
复制代码

发布-订阅的顺序探讨

我们通常所看到的都是先订阅再发布,但是必须要遵守这种顺序吗?答案是不一定的。如果发布者先发布一条消息,但是此时还没有订阅者订阅此消息,我们可以不让此消息消失于宇宙之中。就如同QQ离线消息一样,离线的消息被保存在服务器中,接收人下次登录之后,才会收到此消息。同样的,我们可以建立一个存放离线事件的堆栈,当事件发布的时候,如果此时还没有订阅者订阅这个事件,我们暂时把发布事件的动作包裹在一个函数里,这些包装函数会被存入堆栈中,等到有对象来订阅事件的时候,我们将遍历堆栈并依次执行这些包装函数,即重发里面的事件,不过离线事件的生命周期只有一次,就像qq未读消息只会提示你一次一样。

JavaScript实现发布-订阅模式的便利性

因为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();
复制代码

小结

  • 发布-订阅的优势很明显,做到了时间上的解耦和对象之间的解耦,从架构上看,MVC,MVVM都少不了发布-订阅的参与,我们常用的Vue也是基于发布-订阅的,最近会抽时间写下vue的源码实现,同样的node中的EventEmitter也是发布订阅的,之前也手写过它的实现。
  • 发布-订阅同时也是有缺点存在的,创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息以后,可能此消息最后都未发生,但是这个订阅者会始终存在于内存中。如果程序中大量使用发布-订阅的话,也会使得程序跟踪bug变得困难。

Javascript设计模式之发布-订阅模式相关推荐

  1. JavaScript设计模式之发布-订阅模式(观察者模式)-Part1

    <JavaScript设计模式与开发实践>读书笔记. 发布-订阅模式又叫观察者模式,它定义了对象之间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖它的对象都将得到通知. 例如 ...

  2. JavaScript 设计模式之发布-订阅模式(上)

    什么是发布订阅模式? 发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知.在JavaScript开发中,我们一般用事件模型来替 ...

  3. JavaScript 设计模式之发布-订阅模式(下)

    发布-订阅模式的通用实现 现在我们在上一篇文章中已经看到了如何让售楼处拥有接受订阅和发布事件的功能.假设现在小明又去另一个售楼处买房子,那么这段代码是否必须在另一个售楼处对象上重写一次呢,有没有办法可 ...

  4. 设计模式-观察者模式 发布/订阅模式

    设计模式-观察者模式 发布/订阅模式 代码 观察者接口 public interface IHanFeiZi{// 当吃早饭时public void havBreakFast();// 进行娱乐活动时 ...

  5. Javascript中理解发布--订阅模式

    Javascript中理解发布--订阅模式 阅读目录 发布订阅模式介绍 如何实现发布--订阅模式? 发布---订阅模式的代码封装 如何取消订阅事件? 全局--发布订阅对象代码封装 理解模块间通信 回到 ...

  6. 【设计模式】692- TypeScript 设计模式之发布-订阅模式

    前言 在之前两篇自测清单中,和大家分享了很多 JavaScript 基础知识,大家可以一起再回顾下~ 本文是我在我们团队内部"「现代 JavaScript 突击队」"分享的一篇内容 ...

  7. 从东京奥运会看js设计模式之发布订阅模式

    开篇废话:本篇文章介绍发布-订阅模式,想必很多人听说过有一种观察者模式,网上既有资料说这是两种不同的设计模式,也有说这是一种模式,我倾向于认同他们是同一种设计模式.不必过于纠结 开篇楔子:东京奥运会已 ...

  8. 设计模式之发布订阅模式

    发布--订阅模式简介 发布--订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,多个观察者对象都依赖于一个目标对象,当目标对象的状态发生变化时,所有依赖于这个对象的观察者对象都会收到通知. ...

  9. 游戏服务器架构-设计模式之发布订阅模式

    发布订阅模式场景 熟悉消息中间件的同学应该对发布/订阅模式(Publish Subscribe Pattern)并不陌生.即使你不了解消息中间件,那么在平时生活中发布/订阅模式也是非常常见的场景. 比 ...

最新文章

  1. 每天一道LeetCode-----判断某棵树是否是二叉搜索树
  2. tomcat 虚拟路径 与 虚拟主机配置
  3. ORACLE中BFILE字段的使用研究
  4. sklearn学习 5.降维算法PCA和SVD
  5. 阿里开源深度学习框架XDL,面向高维稀疏数据,支持千亿参数训练规模
  6. fiddler限速/弱网模拟
  7. asp.net 获取当前页面html代码,在asp.net中获取当前页面的URL的方法(推荐)
  8. 三菱PLC编程软件Work2的FB块加密后的解密方法
  9. JDK自带javap命令反编译class文件和Jad反编译class文件(推荐使用jad)
  10. http服务(nginx、apache)停用不安全的SSL协议、TLS1.0和TLS1.1协议/启用TLS1.3
  11. Microsoft Teams 深度使用体验——创建团队
  12. 基于来信码的短信通知平台
  13. 基于MATLAB的人脸识别提纲
  14. 根据AD账号直接单点登录到第三方系统
  15. Policy Gradient连续动作 tf.distributions.Normal log_prob = self.normal_dist.log_prob(self.a) 的解释
  16. JAVA社招,让老板心动的简历原来是这样
  17. 陌生人社交网络大起底:谁是下一个陌陌?
  18. 做影视剪辑小伙伴们的福音,9个影视素材网站,抓紧收藏
  19. 仿京东商城左侧商品分类导航-JS网页特效
  20. 烟花仓库智能管理-RFID仓库管理解决方案-智能RFID仓库管理系统-新导智能

热门文章

  1. Dubbo消费者代理的创建
  2. html文档包包含几个基本标记,HTML中包含哪些基本的标记?
  3. 利用Kubernetes搭建便携式开发环境之MySQL和Redis
  4. [翻译]PHP中define()和const定义常量的区别
  5. RocketMQ 4.5.1 环境搭建
  6. Docker容器(container)详解
  7. 做完项目,对css样式有新发现新感悟
  8. Kerberos学习(一)
  9. nodejs通过响应回写的方式渲染页面资源
  10. E13- terminal is not big enough