发布-订阅模式,定义了对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都将得到通知。发布-订阅模式是使用比较广泛的一种模式,尤其是在异步编程中。

/*
* pre:发布-订阅模式
* 一种一对多的关系
*/
// ------ 示例1 ----------
/**
* 示例:售楼处售楼,购买者询问价格,售楼MM每天要接N多个电话,内容大致都相同,这很是麻烦。
* 于是售楼MM想到把他们的电话号码记在花名册上,每次一有楼盘的信息,就挨个给他们发消息。
* 我们将这个过程,抽象为代码如下:
*/
var salesOffice = {};
salesOffice.clientList = [];
salesOffice.listen = function(fn) {
this.clientList.push(fn);
};
salesOffice.trigger = function() {
for(var i = 0, fn; fn = this.clientList[i++];) {
fn.apply(this, arguments);
}
};
salesOffice.listen(function(price, squareMeter) { // 订阅者1
console.log("price:" + price);
console.log("squareMeter:" + squareMeter);
});
salesOffice.listen(function(price, squareMeter) { // 订阅者2
console.log("定价:" + price);
console.log("平方数:" + squareMeter);
});
salesOffice.trigger(2000000, 70); // 发布
// ----- 示例2 --------
/* 示例1的不足:订阅者收到了发布者发布的每一条消息,但每个订阅者关注的可能不一样,
* 比如小明关注80平米左右的房子,小王关注100平米以上的房子。
* 接下来我们修改程序,示例代码如下:
*/
var salesOffice = {};
salesOffice.clientList = {}; // 使用对象字面量,进行缓存
salesOffice.listen = function(key, fn) {
if(!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
};
salesOffice.trigger = function() {
var key = Array.prototype.shift.apply(arguments);
if(this.clientList.size == 0 || !this.clientList[key]) {
console.log("订阅者为空.");
return;
}
for(var fn of this.clientList[key]) {
fn.apply(this, arguments);
}
};
salesOffice.listen(8, function(price, squareMeter) {
console.log("类型:" + 8 + ",price:" + price + ",squareMeter:" + squareMeter);
});
salesOffice.listen(10, function(price, squareMeter) {
console.log("类型:" + 10 + ",price:" + price + ",squareMeter:" + squareMeter);
}); salesOffice.trigger(8, 2000000, 88);
//----------- 示例3 ------------
/* 试想:如果其他售楼处,也想有发布-订阅功能,是否要将代码重写一次呢?完全没必要。
* 接下来,我们将上面的代码进行抽象,将客户缓存,以及监听和发布事件提取出来。
* 给需要的对象进行浅拷贝。
*/
var event = {
clientList: {},
listen: function(key, fn) {
if(!this.clientList.hasOwnProperty(key)) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function() {
var key = Array.prototype.shift.call(arguments);
if(this.clientList.size == 0 || !this.clientList[key]) {
console.log("订阅者为空.");
return;
}
for(var fn of this.clientList[key]) {
fn.apply(this, arguments);
}
}
};
var installEvent = function(obj) {
for(var k in event) {
obj[k] = event[k];
}
};
var salesOffice = {};
installEvent(salesOffice);
salesOffice.listen(9, function(price, squareMeter) {
console.log("key:9" + ",price:" + price + ",squareMeter:" + squareMeter);
});
salesOffice.listen(10, function(price, squareMeter) {
console.log("key:10" + ",price:" + price + ",squareMeter:" + squareMeter);
});
salesOffice.trigger(9, 3000000, 91);
//----------- 示例4 -------------
/* 增加 - 删除订阅功能
* 如果只传订阅类型,则删除这一组订阅者。
* 如果传了订阅类型,以及订阅者,则删除该订阅者
*/
var event = {
clientList: {},
listen: function(key, fn) {
if(!this.clientList.hasOwnProperty(key)) {
this.clientList[key] = [];
}
this.clientList[key].push(fn);
},
trigger: function() {
var key = Array.prototype.shift.call(arguments);
if(this.clientList.size == 0 || !this.clientList[key]) {
console.log("订阅者为空.");
return;
}
for(var fn of this.clientList[key]) {
fn.apply(this, arguments);
}
}
};
event.remove = function(key, fn) {
var fns = this.clientList[key];
if(!fns) {
return false;
}
if(!fn) {
fns.length = 0;
} else {
for(var k in fns) {
if(fns[k] === fn) {
fns.splice(k, 1);
return;
}
}
}
};
var installEvent = function(obj) {
for(var k in event) {
obj[k] = event[k];
}
};
var salesOffice = {};
installEvent(salesOffice);
salesOffice.listen(9, fn1 = function(price, squareMeter) {
console.log("fn1 - key:9" + ",price:" + price + ",squareMeter:" + squareMeter);
});
salesOffice.listen(9, fn2 = function(price, squareMeter) {
console.log("fn2 - key:9" + ",price:" + price + ",squareMeter:" + squareMeter);
});
salesOffice.remove(9, fn2);
salesOffice.trigger(9, 2000000, 90);
// -------------- 示例5 --------------
/* [全局发布-订阅]
* 上面的示例中,如果有两个售楼处,我们就要创建两个对象。现实中,我们会有中介的存在,
* 我们不要关心是哪个售楼处的楼盘,只要我们跟中介说,有xx平米的房子,就给我发消息。
* 于是,我们将上面的程序再一次抽象。
*/
var Event = (function() {
var clientList = {};
var listen = function(key, fn) {
if(!clientList.hasOwnProperty(key)) {
clientList[key] = [];
}
clientList[key].push(fn);
};
var remove = function(key, fn) {
var fns = clientList[key];
if(!fns) {
return false;
}
if(!fn) {
fns && (fn.length = 0);
} else {
for(var a in fns) {
if(fns[a] === fn) {
fns.splice(a, 1);
return;
}
}
}
};
var trigger = function() {
var key = Array.prototype.shift.call(arguments);
var fns = clientList[key];
if(!fns) {
return false;
}
for(var fn of fns) {
fn.apply(this, arguments);
}
};
return {
listen: listen,
remove: remove,
trigger: trigger
}
})();
Event.listen(8, function(price, squareMeter) {
console.log("key:8," + "price:" + price + ",squareMeter:" + squareMeter);
});
Event.trigger(8, 3000000, 83);
//------------ 示例6 -------------
/*
* [先发布-后订阅]
* 在现实中,我们往往需要先发布,后订阅,订阅后消息发送一次
* 接下来修改程序如下:
*
*/
var Event = (function() {
var clientList = {},
cache = {},
listen, remove, trigger;
listen = function(key, fn) {
if(!clientList.hasOwnProperty(key)) {
clientList[key] = [];
}
clientList[key].push(fn);
// 判断是否有未消费的消息
if(!cache.size != 0) {
for(var a in cache) {
var arr = a.split(",");
if(arr[0] != key) {
continue;
}
arr.splice(0, 1);// 移除key,只保留价格和平方数
var fns = cache[a];
if(fns.length == 0) {// 没有消费者
fn.apply(this, arr);
fns.push(fn);
} else {// 消息消费者没有当前的订阅者
var flag = false;
for(var t of fns) {
if(t === fn) {
flag = true;
}
}
if(!flag) {
fn.apply(this, arr);
fns.push(fn);
}
}
}
}
};
remove = function(key, fn) {
var fns = clientList[key];
if(!fns) {
return false;
}
if(!fn) {
fns && (fn.length = 0);
} else {
for(var a in fns) {
if(fns[a] === fn) {
fns.splice(a, 1);
return;
}
}
}
};
trigger = function() {
var p = Array.prototype.join.call(arguments, ",");
var key = Array.prototype.shift.call(arguments);
var fns = clientList[key];
cache[p] = [];
if(!fns) { // 没有订阅者
return false;
}
for(var fn of fns) {
fn.apply(this, arguments);
cache[p].push(fn); // 缓存消费的订阅者
}
};
return {
listen: listen,
remove: remove,
trigger: trigger
}
})();
Event.trigger(8, 3000000, 83);
Event.listen(8, fn1 = function(price, squareMeter) {
console.log("key:8," + "price:" + price + ",squareMeter:" + squareMeter);
}); // ----------- 示例7 ------------
/* [应用]
* 在web系统中,当用户登录成功后,我们需要做很多事情,比如在顶部加载头像,刷新地址等。
* 使用发布-订阅模式,可以帮我们更好的去实现这一功能。
* 示例如下:
*/
var login = {};
installEvent(login);
var header = (function() {
login.listen("loginSuc", function(data) {
header.setAvatar(data.avatar);
});
return {
setAvatar: function(avatar) {
console.log("设置头像:" + avatar);
}
}
})();
var address = (function() {
login.listen("loginSuc", function() {
address.refresh();
});
return {
refresh: function() {
console.log("刷新地址.");
}
}
})();
login.trigger("loginSuc", {
avatar: "xxx"
});
//---------- 示例7 ------------
/* [应用]
* 模块间通信
* 示例:页面上有一个按钮,和一个div,我们每点击一次按钮,
* div里就显示我们点击的次数,我们使用发布-订阅模式实现
* <button id="btn">点我</button>
* <div id="show"></div>
*/
var a = (function() {
var div = document.getElementById("show");
Event.listen("add", function(count) {
div.innerHTML = count;
});
})(); var b = (function() {
var count = 0;
var btn = document.getElementById("btn");
btn.onclick = function() {
Event.trigger("add", ++count);
}
})();
//=============== 总结 =================
/**
* 通过以上的示例,我们可以看到发布-订阅的模式,优点十分明显。
* 优点:1、时间上的解耦;2、对象之间的解耦。非常适用于异步编程,以及对象之间松耦合的实现。
* 但发布-订阅模式也有很多缺点。
* 缺点:1、创建订阅者消耗一定的内存和时间,当你订阅一个消息后,可能这个消息至始至终都没有发生,
* 但订阅者一直存在内存中。
* 2、发布-订阅模式弱化了对象之间的联系,如果过度使用的话,对象之间的必要联系就会被深埋在背后。
* 特别是嵌套使用的时候,理解起来就比较费时。
*/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

JavaScript设计模式_05_发布订阅模式的更多相关文章

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

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

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

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

  3. Javascript设计模式之发布-订阅模式

    简介 发布-订阅模式又叫做观察者模式,他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知. 回忆曾经 作为一名前端开发人员,给DOM节点绑定事件可是再频繁不过 ...

  4. javaScript设计模式:发布订阅模式

    发布订阅模式的思想是在观察者模式的基础上演变而来,在观察者模式中客户端监听到对象某个行为就触发对应任务程序.而在发布订阅模式中依然基于这个核心思想,所以有时候也会将两者认为是同一种设计模式.它们的不同 ...

  5. 第五章 --- 关于Javascript 设计模式 之 发布-订阅模式

    先来个最简单的 发布订阅模式 document.body.addEventListener('click',function(){ alert(123); }); document.body.clic ...

  6. javascript中的发布订阅模式与观察者模式

    这里了解一下JavaScript中的发布订阅模式和观察者模式,观察者模式是24种基础设计模式之一. 设计模式的背景 设计模式并非是软件开发的专业术语,实际上设计模式最早诞生于建筑学. 设计模式的定义是 ...

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

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

  8. [转] Javascript中理解发布--订阅模式

    发布订阅模式介绍 发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知. 现实生活中的发布- ...

  9. 【转】Javascript中理解发布--订阅模式

    Javascript中理解发布--订阅模式 阅读目录 发布订阅模式介绍 发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时 ...

随机推荐

  1. 设备像素,设备独立像素,CSS像素

    之前学了移动端的开发对设备像素.设备独立像素.CSS像素弄得不太清楚,所以趁周末的时间查了一下,稍加整理 一些概念 在进行具体的分析之前,首先得知道下面这些关键性基本概念. CSS像素 CSS像素是W ...

  2. linux重要的守护进程

    重要的守护进程 守护进程(Daemon)通常会随系统启动时激活并随系统关闭时停止,一直在系统后台中默默为用户提供服务: 守护进程名称 用处 crond 计划任务 dhcpd 动态IP地址分配服务(DH ...

  3. Html5-audio标签简介及手机端不自动播放问题

    1.audio:html5音频标签 <audio loop src="/photo/aa.mp3" id="audio" autoplay preload ...

  4. html字体问题

    正如咱们在上一章中解说的那样,HTML元素使页面规划者能够对文档的构造进行符号.HTML标准列出了浏览器应该怎么显现这些元素的攻略.例如,您能够合理地保证强元素的内容将显现粗体.此外,您能够非常信赖大 ...

  5. git 分支的创建与提交

    我们在开发的过程中会遇到很多团队协作的问题,怎么来解决团队合作呢,就是靠分支来管理代码.一般来说一个功能就要创建一个分支,这样才能减少代码的冲突,给开发带来很大的方便. 首先需要克隆代码 git cl ...

  6. Nginx 反向代理&负载均衡

    1.反向代理 当我们请求一个网站时,nginx会决定由哪台服务器提供服务,就是反向代理. nginx只做请求的转发,后台有多个tomcat服务器提供服务,nginx的功能就是把请求转发给后面的服务器, ...

  7. HDU_1009_FatMouse' Trade

    FatMouse' Trade Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  8. QQ互联第三方登陆 redirect uri is illegal(100010)

    想必第一次大家接触QQ第三方登陆都会遇到各种各样的问题,备受折磨,因此,今天我把自己做QQ登陆的过程描述一下,希望给大家提供参考,少走弯路. 在开发之前,我们先了解下QQ登陆的流程 第一:查看熟悉 网 ...

  9. JavaEE开发之记事本完整案例(SpringBoot + iOS端)

    上篇博客我们聊了<JavaEE开发之SpringBoot整合MyBatis以及Thymeleaf模板引擎>,并且在之前我们也聊了<Swift3.0服务端开发(五) 记事本的开发(iO ...

  10. App架构经验总结(转载)

    原文地址:http://www.iteye.com/news/31472 架构因人而异,不同的架构师大多会有不同的看法:架构也因项目而异,不同的项目需求不同,相应的架构也会不同.然而,有些东西还是通用 ...