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 相关文章推荐
在IE下:float属性会影响offsetTop的取值
Dec 22 Javascript
firefox中用javascript实现鼠标位置的定位
Jun 17 Javascript
JavaScript 组件之旅(四):测试 JavaScript 组件
Oct 28 Javascript
jQuery EasyUI API 中文文档 - ComboBox组合框
Oct 07 Javascript
js采用map取到id集合组并且实现点击一行选中一行
Dec 16 Javascript
SinaEditor使用方法详解
Dec 28 Javascript
Jquery 点击按钮自动高亮实现原理及代码
Apr 25 Javascript
jQuery简单几行代码实现tab切换
Mar 10 Javascript
JavaScript cookie详解及简单实例应用
Dec 31 Javascript
JS实现数组按升序及降序排列的方法
Apr 26 Javascript
详解html-webpack-plugin插件(用法总结)
Sep 12 Javascript
浅谈小程序globalData的那些事儿
Nov 01 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防注
2007/01/15 PHP
用php获取远程图片并把它保存到本地的代码
2008/04/07 PHP
PHP按符号截取字符串的指定部分的实现方法
2018/09/10 PHP
PDO::prepare讲解
2019/01/29 PHP
PHP PDOStatement::bindParam讲解
2019/01/30 PHP
Aster vs KG BO3 第二场2.19
2021/03/10 DOTA
关于JavaScript的gzip静态压缩方法
2007/01/05 Javascript
服务端 VBScript 与 JScript 几个相同特性的写法 By shawl.qiu
2007/03/06 Javascript
javascript+css 网页每次加载不同样式的实现方法
2009/12/27 Javascript
用示例说明filter()与find()的用法以及children()与find()的区别分析
2013/04/26 Javascript
使用nodejs、Python写的一个简易HTTP静态文件服务器
2014/07/18 NodeJs
TypeScript 中接口详解
2015/06/19 Javascript
JavaScript实现下拉菜单的显示和隐藏
2016/01/05 Javascript
Node.js环境下Koa2添加travis ci持续集成工具的方法
2017/06/19 Javascript
vue上传图片组件编写代码
2017/07/26 Javascript
vue iview组件表格 render函数的使用方法详解
2018/03/15 Javascript
微信小程序实现美团菜单
2018/06/06 Javascript
微信小程序使用二次贝塞尔曲线画波浪
2018/12/25 Javascript
Vue实现日历小插件
2019/06/26 Javascript
DatePickerDialog 自定义样式及使用全解
2019/07/09 Javascript
js实现筛选功能
2020/11/24 Javascript
Python字符串格式化
2015/06/15 Python
Python3中的真除和Floor除法用法分析
2016/03/16 Python
Python检测网站链接是否已存在
2016/04/07 Python
Python Numpy 控制台完全输出ndarray的实现
2020/02/19 Python
Python自动重新加载模块详解(autoreload module)
2020/04/01 Python
Hotels.com爱尔兰:全球酒店预订
2017/02/24 全球购物
大学同学聚会邀请函
2014/01/19 职场文书
上课睡觉检讨书
2014/01/28 职场文书
给校长的建议书300字
2014/05/16 职场文书
学生安全承诺书
2014/05/22 职场文书
上课玩手机的检讨书
2014/10/01 职场文书
催款函怎么写
2015/06/24 职场文书
解读MySQL的客户端和服务端协议
2021/05/10 MySQL
草系十大最强宝可梦,纸片人上榜,榜首大家最熟悉
2022/03/18 日漫
golang实现浏览器导出excel文件功能
2022/03/25 Golang