JavaScript实现与使用发布/订阅模式详解


Posted in Javascript onJanuary 19, 2019

本文实例讲述了JavaScript实现与使用发布/订阅模式。分享给大家供大家参考,具体如下:

一、发布/订阅模式简介

发布/订阅模式(即观察者模式): 设计该模式背后的主要动力是促进形成松散耦合。在这种模式中,并不是一个对象调用另一个对象的方法,而是一个订阅者对象订阅发布者对象的特定活动,并在发布者对象的状态发生改变后,订阅者对象获得通知。订阅者也称为观察者,而被观察的对象称为发布者或主题。当发生了一个重要的事件时,发布者将会通知(调用)所有订阅者,并且可能经常以事件对象的形式传递消息。

基本思路:发布者对象需要一个数组类型的属性,以存储所有的订阅者。订阅(即注册)就是将新的订阅者加入到这个数组中去,而注销即是从这个数组中删除某个订阅者。此外,发布消息就是循环遍历订阅者列表并通知他们。

二、如何发布订阅者的方法?

这里我的大体思路是对的。不过当时面试时,我还说了“在发布者之外,还需要定义了一个新的类——订阅者。在订阅者中,需要定义了一个类似 getNews 的方法,以便在发布者发布消息时,调用该方法”。然后,面试官说这样太麻烦了,万一订阅者没有这个方法呢?然后,我不是很懂……

于是我就想到了,在发布消息时直接传递了参数:obj.news = msg; 然后面试官说这样不是更麻烦了吗?这样的话,如果订阅者没有 news 这个属性怎么办?还得判断订阅者是否有 news 这个属性,没有的话就会出现 undifined 的报错。

然后,我就不知道该怎么做了……然后面试官为人特别 nice ,告诉我“可以用继承或者是在注册时候就传入一个 function ”。

面试完后,回家上网查相关知识,整理出的注意点如下:

  1. 发送消息,即通知,意味着调用订阅者对象的某个方法。故当用户订阅信息时,该订阅者需要向发布者的 subscribe() 提供它的其中一个方法——这应该就是面试官所说的注册时候就传入一个方法。
  2. 每个发布者对象需要具有以下成员:

subscribers:一个数组,存储订阅者;
subscribe():注册/订阅,将订阅者添加到 subscribers 数组中;
unsubscribe():取消订阅。从 subscribers 数组中删除订阅者;
publish():循环遍历 subscribers 数组中的每一个元素,并且调用它们注册时所提供的方法;
所有这三种方法都需要一个 type 参数。这是因为发布者可能触发多个事件(比如同时发布一本杂志和一份报纸),而订阅者可能仅选择订阅其中一种,而另外一种不订阅。

三、代码实现

参考《 JavaScript 模式》一书,使用字面量实现代码如下:

// 由于这些成员对于任何发布者对象都是通用的,故将它们作为独立对象的一个部分来实现是很有意义的。那样我们可将其复制到任何对象中,并将任意给定对象变成一个发布者。
// 如下实现一个通用发布者,定义发布者对象……
let publisher = {
 subscribers: {
  any: []
 },
 subscribe: function (fn, type = `any`) {
  if (typeof this.subscribers[type] === `undefined`) {
   this.subscribers[type] = [];
  }
  this.subscribers[type].push(fn);
 },
 unSubscribe: function (fn, type = `any`) {
  let newSubscribers = [];
  this.subscribers[type].forEach((item, i) => {
   if (item !== fn) {
    newSubscribers.push(fn);
   }
  });
  this.subscribers[type] = newSubscribers;
 },
 publish: function (args, type = `any`) {
  this.subscribers[type].forEach((item, i) => {
   item(args);
  });
 }
};
// 定义一个函数makePublisher(),它接受一个对象作为参数,通过把上述通用发布者的方法复制到该对象中,从而将其转换为一个发布者
function makePublisher(obj) {
 for (let i in publisher) {
  if (publisher.hasOwnProperty(i) && typeof publisher[i] === `function`) {
   obj[i] = publisher[i];
  }
 }
 obj.subscribers = { any: [] };
}
// 实现paper对象
var paper = {
 daily: function () {
  this.publish(`big news today!`);
 },
 monthly: function () {
  this.publish(`interesting analysis`, `monthly`);
 }
};
// 将paper构造成一个发布者
makePublisher(paper);
// 看看订阅对象joe,该对象有两个方法:
var joe = {
 drinkCoffee: function (paper) {
  console.log(`Just read ` + paper);
 },
 sundayPreNap: function (monthly) {
  console.log(`About to fall asleep reading this ` + monthly);
 }
};
// paper注册joe(即joe向paper订阅)
paper.subscribe(joe.drinkCoffee);
paper.subscribe(joe.sundayPreNap, `monthly`);
// 即joe为默认“any”事件提供了一个可被调用的方法,而另一个可被调用的方法则用于当“monthly”类型的事件发生时的情况。现在让我们来触发一些事件:
paper.daily();   // Just read big news today
paper.daily();   // Just read big news today
paper.monthly();  // About to fall asleep reading this interesting analysis
paper.monthly();  // About to fall asleep reading this interesting analysis
paper.monthly();  // About to fall asleep reading this interesting analysis

我自己又尝试用 ES6 的 class 语法写了一遍(PS:这是转载者自己写的,原文作者是用函数自己又实现了一遍):

// 由于这些成员对于任何发布者对象都是通用的,故将它们作为独立对象的一个部分来实现是很有意义的。那样我们可将其复制到任何对象中,并将任意给定对象变成一个发布者。
// 如下实现一个通用发布者,定义发布者对象……
class Publisher {
 constructor(){
  this.subscribers = {
   any: []
  }
 }
 subscribe(fn, type=`any`){
  if(typeof this.subscribers[type] === `undefined`){
   this.subscribers[type] = [];
  }
  this.subscribers[type].push(fn);
 }
 unSubscribe(fn, type=`any`){
  let newArr = [];
  this.subscribers[type].forEach((item, i) => {
   if(item !== fn){
    newArr.push(fn);
   }
  });
  this.subscribers[type] = newArr;
 }
 publish(args, type=`any`){
  this.subscribers[type].forEach((item, i) => {
   item(args);
  });
 }
 // 定义一个函数makePublisher(),它接受一个对象作为参数,通过把上述通用发布者的方法复制到该对象中,从而将其转换为一个发布者
 static makePublisher(obj){
  obj.publisher = new Publisher();
 }
}
// 实现person对象
var person = {
 sayHi: function(name){
  this.publisher.publish(name);
 },
 sayAge: function(num){
  this.publisher.publish(num, `age`);
 }
}
// 将person构造成一个发布者
Publisher.makePublisher(person);
// 看看订阅对象myLover,该对象有两个方法:
var myLover = {
 name: ``,
 hello: function(name){
  this.name = name;
  console.log(`Hi, i am ` + name + ` ! Nice to meet you!`);
 },
 timeOfLife: function(num){
  console.log(`Hello! My name is ` + this.name + ` ! I am ` + num + ` years old!`);
 }
}
// person注册myLover(即myLover向person订阅)
person.publisher.subscribe(myLover.hello);
person.publisher.subscribe(myLover.timeOfLife, `age`);
// 即myLover为默认“any”事件提供了一个可被调用的方法,而另一个可被调用的方法则用于当“age”类型的事件发生时的情况。现在让我们来触发一些事件:
person.sayHi(`Jimmy`);  // Hi, i am Jimmy ! Nice to meet you!
person.sayAge(24);    // Hello! My name is Jimmy ! I am 24 years old!
person.sayHi(`Tom`);   // Hi, i am Tom ! Nice to meet you!
person.sayAge(6);     // Hello! My name is Tom ! I am 6 years old!
person.sayAge(18);    // Hello! My name is Tom ! I am 18 years old!

更多关于JavaScript相关内容还可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
用jscript实现新建和保存一个word文档
Jun 15 Javascript
jQuery中与toggleClass等价的程序段 以及未来学习的方向
Mar 18 Javascript
JavaScript中为元素加上name属性的方法
May 09 Javascript
基于jquery实现日历签到功能
Sep 11 Javascript
基于JS实现textarea中获取动态剩余字数的方法
May 25 Javascript
使用JavaScript获取URL中的参数(两种方法)
Nov 16 Javascript
基于jQuery实现弹幕APP
Feb 10 Javascript
js统计页面上每个标签的数量实例代码
May 29 Javascript
微信小程序实现登录遮罩效果
Nov 01 Javascript
Vue之beforeEach非登录不能访问的实现(代码亲测)
Jul 18 Javascript
Vue组件间通信 Vuex的用法解析
Aug 05 Javascript
Vue-cli3生成的Vue项目加载Mxgraph方法示例
May 31 Javascript
Vuex中的State使用介绍
Jan 19 #Javascript
为什么要使用Vuex的介绍
Jan 19 #Javascript
Vue核心概念Getter的使用方法
Jan 18 #Javascript
Vue唯一可以更改vuex实例中state数据状态的属性对象Mutation的讲解
Jan 18 #Javascript
JavaScript数据结构之栈实例用法
Jan 18 #Javascript
Vue核心概念Action的总结
Jan 18 #Javascript
js取小数点后两位四种方法
Jan 18 #Javascript
You might like
php实现的MySQL通用查询程序
2007/03/11 PHP
ie6 动态缩略图不显示的原因
2009/06/21 PHP
如何使用php判断服务器是否是HTTPS连接
2013/07/05 PHP
PHP截断标题且兼容utf8和gb2312编码
2013/09/22 PHP
php解析json数据实例
2014/08/19 PHP
php时间计算相关问题小结
2016/05/09 PHP
javascript 写类方式之五
2009/07/05 Javascript
jQuery scroll事件实现监控滚动条分页示例
2014/04/04 Javascript
Jquery插件分享之气泡形提示控件grumble.js
2014/05/20 Javascript
jquery实现红色竖向多级向右展开的导航菜单效果
2015/08/31 Javascript
jquery计算出left和top,让一个div水平垂直居中的简单实例
2016/07/13 Javascript
AngularJs 国际化(I18n/L10n)详解
2016/09/01 Javascript
vue项目中做编辑功能传递数据时遇到问题的解决方法
2016/12/19 Javascript
js微信应用场景之微信音乐相册案例分享
2017/08/11 Javascript
vue一个页面实现音乐播放器的示例
2018/02/06 Javascript
vue实现密码显示与隐藏按钮的自定义组件功能
2019/04/23 Javascript
[15:15]教你分分钟做大人:狙击手
2014/10/30 DOTA
Python3基础之条件与循环控制实例解析
2014/08/13 Python
python通过pil将图片转换成黑白效果的方法
2015/03/16 Python
python 环境变量和import模块导入方法(详解)
2017/07/11 Python
python+logging+yaml实现日志分割
2019/07/22 Python
Python线上环境使用日志的及配置文件
2019/07/28 Python
python中eval与int的区别浅析
2019/08/11 Python
Python解析json代码实例解析
2019/11/25 Python
PyTorch的自适应池化Adaptive Pooling实例
2020/01/03 Python
一文轻松掌握python语言命名规范规则
2020/06/18 Python
Javascript 高级手势使用介绍
2013/04/21 HTML / CSS
Html5 Canvas实现图片标记、缩放、移动和保存历史状态功能 (附转换公式)
2020/03/18 HTML / CSS
英国第一的滑雪服装和装备零售商:Snow+Rock
2020/02/01 全球购物
幼儿园春季开学寄语
2014/04/03 职场文书
家长建议怎么写
2014/05/15 职场文书
2014年教师节寄语
2014/08/11 职场文书
查摆问题整改措施范文
2014/10/11 职场文书
2014年高数考试作弊检讨书
2014/12/14 职场文书
2014保险公司内勤工作总结
2014/12/16 职场文书
微信小程序实现拍照和相册选取图片
2021/05/09 Javascript