用 JavaScript 实现发布/订阅模式
来源:互联网 发布:无损音乐软件 编辑:程序博客网 时间:2024/05/21 06:02
重要申明:感谢原文作者——杠子,想看原文请戳这里!我转载了原文,修改了一些原文行文不通畅之处,并更改附上了自己的代码段,望请勿怪。挂在自己的博客下面一方面是为了收藏好文,方便自己温顾而知新;另一方面也希望更多人看到这文章。PS:原文撰写于 2016-08-11。
一、发布/订阅模式简介
发布/订阅模式(即观察者模式): 设计该模式背后的主要动力是促进形成松散耦合。在这种模式中,并不是一个对象调用另一个对象的方法,而是一个订阅者对象订阅发布者对象的特定活动,并在发布者对象的状态发生改变后,订阅者对象获得通知。订阅者也称为观察者,而被观察的对象称为发布者或主题。当发生了一个重要的事件时,发布者将会通知(调用)所有订阅者,并且可能经常以事件对象的形式传递消息。
基本思路:发布者对象需要一个数组类型的属性,以存储所有的订阅者。订阅(即注册)就是将新的订阅者加入到这个数组中去,而注销即是从这个数组中删除某个订阅者。此外,发布消息就是循环遍历订阅者列表并通知他们。
二、如何发布订阅者的方法?
这里我的大体思路是对的。不过当时面试时,我还说了“在发布者之外,还需要定义了一个新的类——订阅者。在订阅者中,需要定义了一个类似 getNews
的方法,以便在发布者发布消息时,调用该方法”。然后,面试官说这样太麻烦了,万一订阅者没有这个方法呢?然后,我不是很懂……
于是我就想到了,在发布消息时直接传递了参数:obj.news = msg;
然后面试官说这样不是更麻烦了吗?这样的话,如果订阅者没有 news 这个属性怎么办?还得判断订阅者是否有 news 这个属性,没有的话就会出现 undifined 的报错。
然后,我就不知道该怎么做了……然后面试官为人特别 nice ,告诉我“可以用继承或者是在注册时候就传入一个 function ”。
面试完后,回家上网查相关知识,整理出的注意点如下:
发送消息,即通知,意味着调用订阅者对象的某个方法。故当用户订阅信息时,该订阅者需要向发布者的
subscribe()
提供它的其中一个方法——这应该就是面试官所说的注册时候就传入一个方法。每个发布者对象需要具有以下成员:
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 todaypaper.daily(); // Just read big news todaypaper.monthly(); // About to fall asleep reading this interesting analysispaper.monthly(); // About to fall asleep reading this interesting analysispaper.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 设计模式 发布订阅模式
- 发布订阅者模式c++实现
- 订阅发布模式——C++实现
- 【JavaScript设计模式】行为型设计模式--发布-订阅模式
- Javascript设计模式-07-观察者模式(发布订阅模式)
- Node中EventEmitter以及如何实现JavaScript中的订阅/发布模式
- Javascript设计模式-发布/订阅模式最小化的示例
- 浅析JavaScript设计模式——发布-订阅/观察者模式
- JavaScript发布/订阅实例
- 发布-订阅消息模式
- 订阅发布模式
- jquery订阅发布模式
- 发布-订阅消息模式
- 进程间的通信方式?
- 洛谷p1330 封锁阳光大学-二分图染色
- 教你怎么编写高效的JSON工具类
- 【Spring】专业术语了解(二)
- 如何科学地蹭热点:用python爬虫获取热门微博评论并进行情感分析
- 用 JavaScript 实现发布/订阅模式
- 机器学习_特征选择
- 数据库优化--编码阶段
- Hot Air Ballooning
- PAT 甲级 1074. Reversing Linked List (25)
- 分布式系统中生成全局ID的总结与思考
- 探长需要你!我们还上班!
- 堵在路上的你,真的要好好补一补了……
- 利用微信监管MXNet训练