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

一、简单的发布-订阅模式

这里以售楼处的售楼消息为例,小明和小红是消息订阅者,售楼处是发布消息者,一旦售楼处有消息就会主动向小明和小红发送消息
var salesOffices = {};  // 定义售楼处
salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数
salesOffices.listen = function( fn ){ // 增加订阅者
this.clientList.push( fn ); // 订阅的消息添加进缓存列表
};
salesOffices.trigger = function(){ // 发布消息
for( var i = 0, fn; fn = this.clientList[ i++ ];) {
fn.apply( this, arguments ); // arguments 是发布消息时带上的参数
}
};
下面我们来进行一些简单的测试:
salesOffices.listen( function( price, squareMeter ) {   // 小明订阅者
console.log( '价格= ' + price );
console.log( 'squareMeter= ' + squareMeter );
}); salesOffices.listen( function( price, squareMeter ) { // 小红订阅者
console.log( '价格= ' + price );
console.log( 'squareMeter= ' + squareMeter );
}); salesOffices.trigger( 2000000, 88 ); // 输出: 价格= 2000000,squareMeter= 88 价格= 2000000,squareMeter= 88
salesOffices.trigger( 3000000, 110 ); // 输出: 价格= 3000000,squareMeter= 110 价格= 3000000,squareMeter= 110
这里还存在一些问题。我们看到订阅者接收到了发布者发布的每个消息,虽然小明只想买 88 平方米的房子,但是发布者把110平方米的信息也推送给了小明,这对小明来说是不必要的困扰。所以我们有必要增加一个标示key, 让订阅者只订阅自己感兴趣的消息。所以我们需要改造一下代码:
var salesOffices = {};  // 定义售楼处
salesOffices.clientList = {}; // 缓存列表,存放订阅者的回调函数
salesOffices.listen = function( key, fn ){ // 增加订阅者
if(!this.clientList[key]) {
this.clientList[key] = []
}
this.clientList[key].push( fn ); // 订阅的消息添加进缓存列表
}; salesOffices.trigger = function(){ // 发布消息
var key = Array.prototype.shift.call( arguments ), // 取出消息类型
fns = this.clientList[ key ]; // 取出该消息对应的回调函数集合
if ( !fns || fns.length === 0 ){ // 如果没有订阅该消息,则返回
return false;
}
for( var i = 0, fn; fn = fns[ i++ ];) {
fn.apply( this, arguments ); // arguments 是发布消息时带上的参数
}
}; salesOffices.listen( 'squareMeter88', function( price ) { // 小明订阅者
console.log( '价格= ' + price );
}); salesOffices.listen( 'squareMeter110', function( price ) { // 小红订阅者
console.log( '价格= ' + price );
}); salesOffices.trigger( 'squareMeter88', 2000000); // 输出: 价格= 2000000
salesOffices.trigger( 'squareMeter110', 3000000); // 输出: 价格= 3000000

二、发布-订阅模式的通用实现

这段代码是否必须在另一个售楼处对象上重写一次呢,有没有办法可以让所有对象都拥有发布—订阅功能呢? 
首先,我们把发布—订阅的功能提取出来,放在一个单独的对象内:
var event = {
clientList: [],
listen: function( key, fn ){
if ( !this.clientList[ key ] ){
this.clientList[ key ] = [];
}
this.clientList[key].push( fn ); // 订阅的消息添加进缓存列表
},
trigger: function() {
var key = Array.prototype.shift.call( arguments ), // (1);
fns = this.clientList[ key ];
if ( !fns || fns.length === 0 ){ // 如果没有绑定对应的消息
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments ); // (2) // arguments 是 trigger 时带上的参数
}
}
}
再定义一个 installEvent 函数,这个函数可以给所有的对象都动态安装发布—订阅功能:
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
};
var salesOffices = {};
installEvent( salesOffices );
salesOffices.listen( 'squareMeter88', function( price ){
console.log( '价格= ' + price ); // 小明订阅消息
});
salesOffices.listen( 'squareMeter100', function( price ){
console.log( '价格= ' + price ); // 小红订阅消息
});
salesOffices.trigger( 'squareMeter88', 2000000 ); // 输出:2000000
salesOffices.trigger( 'squareMeter100', 3000000 ); // 输出:3000000
取消订阅的事件:
event.remove = function( key, fn ){
var fns = this.clientList[ key ];
if ( !fns ){ // 如果 key 对应的消息没有被人订阅,则直接返回
return false;
}
if ( !fn ){ // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
fns && ( fns.length = 0 );
}else{
for ( var l = fns.length - 1; l >=0; l-- ){ // 反向遍历订阅的回调函数列表
var _fn = fns[ l ];
if ( _fn === fn ){
fns.splice( l, 1 ); // 删除订阅者的回调函数
}
}
}
}; var salesOffices = {};
var installEvent = function( obj ){
for ( var i in event ){
obj[ i ] = event[ i ];
}
}
installEvent( salesOffices ); salesOffices.listen( 'squareMeter88', fn1 = function( price ){ // 小明订阅消息
console.log( '价格= ' + price );
});
salesOffices.listen( 'squareMeter88', fn2 = function( price ){ // 小红订阅消息
console.log( '价格= ' + price );
});
salesOffices.remove( 'squareMeter88', fn1 ); // 删除小明的订阅
salesOffices.trigger( 'squareMeter88', 2000000 ); // 输出:2000000

三、全局的发布-订阅对象

发布—订阅模式可以用一个全局的 Event 对象来实现,订阅者不需要了解消 息来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event 作为一个类似“中介者” 的角色,把订阅者和发布者联系起来
var Event = (function(){
var clientList = {},
listen,
trigger,
remove;
listen = function( key, fn ){
if ( !clientList[ key ] ){
clientList[ key ] = [];
}
clientList[ key ].push( fn );
};
trigger = function(){
var key = Array.prototype.shift.call( arguments ),
fns = clientList[ key ];
if ( !fns || fns.length === 0 ){
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments );
}
}; remove = function( key, fn ){
var fns = clientList[ key ];
if ( !fns ){
return false;
}
if ( !fn ){
fns && ( fns.length = 0 );
} else {
for ( var l = fns.length - 1; l >=0; l-- ){
var _fn = fns[ l ];
if(_fn === fn){
fns.splice( l, 1 );
}
}
}
}; return {
listen: listen,
trigger: trigger,
remove: remove
}
})(); Event.listen( 'squareMeter88', function( price ){ // 小红订阅消息
console.log( '价格= ' + price ); // 输出:'价格=2000000'
});
Event.trigger( 'squareMeter88', 2000000 ); // 售楼处发布消息

四、总结

  发布—订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。它的应用非常 广泛,既可以用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布—订阅模式还可 以用来帮助实现一些别的设计模式,比如中介者模式。从架构上来看,无论是 MVC 还是 MVVM, 都少不了发布—订阅模式的参与,而且 JavaScript 本身也是一门基于事件驱动的语言。
  当然,发布—订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存,而 且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外, 发布—订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联 系也将被深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一 起的时候,要跟踪一个 bug 不是件轻松的事情。
 

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

  1. javascript设计模式——发布订阅模式

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

  2. javascript 设计模式 -- 发布/订阅模式

    直接上代码: index.html : <!DOCTYPE html> <html lang="en"> <head> <meta cha ...

  3. 设计模式-发布订阅模式(javaScript)

    1. 前言 2. 什么是发布订阅模式 3. 发布订阅优缺点 4. 举例 4. 总结 1. 前言 发布订阅者模式是为了发布者和订阅者之间避免产生依赖关系,发布订阅者之间的订阅关系由一个中介列表来维护.发 ...

  4. js设计模式--发布订阅模式

    前言 本系列文章主要根据<JavaScript设计模式与开发实践>整理而来,其中会加入了一些自己的思考.希望对大家有所帮助. 概念 发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的 ...

  5. [转]js设计模式—发布订阅模式

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

  6. [转] 浅析JavaScript设计模式——发布-订阅/观察者模式

    前一段时间一直在写CSS3的文章 一直都没写设计模式 今天来写写大名鼎鼎观察者模式 先画张图 观察者模式的理解 我觉得还是发布-订阅模式的叫法更容易我们理解 (不过也有的书上认为它们是两种模式……)  ...

  7. js设计模式-发布/订阅模式

    一.前言 发布订阅模式,基于一个主题/事件通道,希望接收通知的对象(称为subscriber)通过自定义事件订阅主题,被激活事件的对象(称为publisher)通过发布主题事件的方式被通知. 就和用户 ...

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

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

  9. 理解JavaScript设计模式与开发应用中发布-订阅模式的最终版代码

    最近拜读了曾探所著的<JavaScript设计模式与开发应用>一书,在读到发布-订阅模式一章时,作者不仅给出了基本模式的通用版本的发布-订阅模式的代码,最后还做出了扩展,给该模式增加了离线 ...

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

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

随机推荐

  1. python之json读写

    #将字典转json并写入文件 import json i=3 j=5 a={'a':i,'b':j} js=json.dumps(a) print(js) with open("/Users ...

  2. moveUp()

    这个函数内容有点多,想讲一下大概思路: 向上移有两种情况1.前面为空白 这种情况有两个步骤 (1)将人当前的位置设置为空白(0), (2)再讲人前面的位置设置为人(2)2.前面为箱子 当前面为箱子时有 ...

  3. Leetcode题目64.最小路径和(动态规划-中等)

    题目描述: 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 示例: 输入: [ [1,3,1], [1, ...

  4. Linux如何修改环境变量

    如果想要环境变量永久生效可以修改下面两个文件中的任何一个: 1 /etc/profile 2 .bash_profile 其中,/etc/profile是全局的环境变量,对所有用户生效,而.bash_ ...

  5. Flutter制作Toast会自己关闭的消息提示框

    项目中需要用到类似安卓的Toast提示框,因为flutter中又没有相关组件,然后在网上看到个不错的,地址https://www.jianshu.com/p/cf7877c9bdeb,然后拿过来修改了 ...

  6. 在windows系统搭建并运行一个Flutter项目

    搭建Flutter之前需要已经安装好相应的Flutter开发环境,如果没安装好相应环境的可以查看在windows系统搭建Flutter开发环境 搭建Flutter项目可以通过命令行搭建,或者通过and ...

  7. qt 之http学习

    在Qt网络编程中,需要用到协议,即HTTP.它是超文本传输协议,它是一种文件传输协议. 新建工程名为“http”,然后选中QtNetwork模块,最后Base class选择QWidget.注意:如果 ...

  8. 计算机组成原理 — GPU 图形处理器

    目录 文章目录 目录 显卡 GPU GPU 与深度学习 GPU 与 CPU 体系结构的区别 GPU 显存与 CPU 主存的区别 GPU 与 CPU 之间的数据交互方式 GPU 的体系结构 GPU 的工 ...

  9. Scala面向对象03

  10. git学习教程二之远程仓库学习

    首先你需要注册一个github用户名,我的github账户是:1654218052@qq.com 由于本地的git仓库和github的仓库是通过SSH加密的,所以我们还需要设置一点东西哦 第1步:创建 ...