Javascript设计模式之发布-订阅模式
简介
发布-订阅模式又叫做观察者模式,他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知。
回忆曾经
作为一名前端开发人员,给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('李四');

- 以上就是一个简单的发布-订阅的实现,但是我们会发现订阅者会收到发布者发布的每一条信息,如果李四比较阴暗,不想听到你结婚的消息,只想听到你的坏消息,比如你被开除了,他就心里高兴。这时候我们就需要加一个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设计模式之发布-订阅模式的更多相关文章
- [转] JavaScript设计模式之发布-订阅模式(观察者模式)-Part1
<JavaScript设计模式与开发实践>读书笔记. 发布-订阅模式又叫观察者模式,它定义了对象之间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖它的对象都将得到通知. 例如 ...
- JavaScript设计模式(发布订阅模式)
发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知.在JavaScript开发中,我们一般用事件模型来替代传统的发布—订阅模式 ...
- javaScript设计模式:发布订阅模式
发布订阅模式的思想是在观察者模式的基础上演变而来,在观察者模式中客户端监听到对象某个行为就触发对应任务程序.而在发布订阅模式中依然基于这个核心思想,所以有时候也会将两者认为是同一种设计模式.它们的不同 ...
- 第五章 --- 关于Javascript 设计模式 之 发布-订阅模式
先来个最简单的 发布订阅模式 document.body.addEventListener('click',function(){ alert(123); }); document.body.clic ...
- JavaScript设计模式_05_发布订阅模式
发布-订阅模式,定义了对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都将得到通知.发布-订阅模式是使用比较广泛的一种模式,尤其是在异步编程中. /* * pre:发布-订阅 ...
- javascript中的发布订阅模式与观察者模式
这里了解一下JavaScript中的发布订阅模式和观察者模式,观察者模式是24种基础设计模式之一. 设计模式的背景 设计模式并非是软件开发的专业术语,实际上设计模式最早诞生于建筑学. 设计模式的定义是 ...
- Javascript中理解发布--订阅模式
Javascript中理解发布--订阅模式 阅读目录 发布订阅模式介绍 如何实现发布--订阅模式? 发布---订阅模式的代码封装 如何取消订阅事件? 全局--发布订阅对象代码封装 理解模块间通信 回到 ...
- [转] Javascript中理解发布--订阅模式
发布订阅模式介绍 发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知. 现实生活中的发布- ...
- 【转】Javascript中理解发布--订阅模式
Javascript中理解发布--订阅模式 阅读目录 发布订阅模式介绍 发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时 ...
随机推荐
- Android MVC MVP MVVM (三)
MVVM Model-View-ViewModel的简写 在MVP基础上实现数据视图的DataBinding,数据变化,视图自动变化,反之也成立. DataBinding 启用DataBinding ...
- day34 并行并发、进程开启、僵尸及孤儿进程
day34 并行并发.进程开启.僵尸及孤儿进程 1.并行与并发 什么是并行? 并行指的是多个进程同时被执行,是真正意义上的同时 什么是并发? 并发指的是多个程序看上去被同时执行,这是因为cpu在多个程 ...
- 微信图片上传 wx.Imagechoose
拍照或从手机相册中选图接口 wx.chooseImage({ count: 1, // 默认9 sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩 ...
- 这可能是最简单易懂的 ZooKeeper 笔记
分布式架构 CAP 与 BASE 理论 一致性协议 初识 Zookeeper Zookeeper 介绍 Zookeeper 工作机制 Zookeeper 特点 Zookeeper 数据结构 Zooke ...
- 输出1-n的全排列dfs
https://ac.nowcoder.com/acm/contest/998/C #include<stdio.h> #include<iostream> #include ...
- 今天遇到了不能创建mysql函数
今天用navicat 不能创建函数,查询了 MySQL函数不能创建,是未开启功能: mysql> show variables like '%func%'; +----------------- ...
- k8s之statefulSet-有状态应用副本集控制器
1.概述 无状态应用更关注群体,任何一个成员都可以被取代,有状态应用关注的是个体.用deployment控制器管理的nginx.myapp等都属于无状态应用,像mysql.redis.zookeepe ...
- luogu P2765 魔术球问题 (最小路径覆盖)
大意:给定n根柱子, 依次放入1,2,3,...的球, 同一根柱子相邻两个球和为完全平方数, 求最多放多少个球. 对和为平方数的点连边, 就相当于求DAG上最小路径覆盖. #include <i ...
- 怎样理解 Vue 的 "Hello, World!" 代码?
直接复制以下代码到 html 文件中即可运行. <!DOCTYPE html> <html> <head> <meta charset="utf-8 ...
- thymeleaf 模板使用 之 前台界面获取后台属性值
使用Thymeleaf模板时,如果需要在js中获取后台传值,那么需要用内联JS写法获取 [姿势很重要] 一.后台通过Model的addAttribute方法向前台传值 1.js获取后台属性值(--内联 ...