什么是发布-订阅模式
以我们使用过的 documeng.body.addEventListener(“click”,function(){}) 为例,这样的就是发布-订阅模式的具体实现。
我们订阅了 documeng.body 上的 click 事件,当被点击的时候 body 节点便会向订阅者发布者消息。
如果用生活中的例子举例,那就是我们订阅微信公众号,公众号发送消息,订阅的用户就会接收到消息。
基于以上的例子,我们总结出发布订阅模式的三要素:
- 一个订阅者
- 一个发布者
- 一个处理 订阅和发布的中间人
接下来让我们来实现一个发布-订阅模式。
我们规定 listen-订阅 trigger-发布 -remove 取消订阅
一个简单的发布订阅模式
调用的形式如下:
1 2 3 4 5 6
| salesOffices.listen(function (price) { console.log("价格", price); });
salesOffices.trigger(100); salesOffices.remove(100);
|
具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| var salesOffices = {}; salesOffices.clientList = [];
salesOffices.listen = function (fn) { this.clientList.push(fn); };
salesOffices.trigger = function () { for (let i = 0; i < this.clientList.length; i++) { this.clientList[i].apply(this, arguments); } }; salesOffices.remove = function () { this.clientList.length = 0; };
salesOffices.listen(function (price) { console.log("价格", price); });
salesOffices.listen(function (price) { console.log("价格", price); });
salesOffices.remove();
salesOffices.trigger(100); salesOffices.trigger(50);
|
带标识(key)的发布订阅
上一小节中的代码没有订阅标识,一次发布所有用户都会收到,我们改进一下代码让变成谁订阅谁接收
调用方式改成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function fn1(price) { console.log("A价格", price); }
salesOffices.listen("A", fn1);
salesOffices.listen("A", function () { console.log("第二个 A"); });
salesOffices.remove("A");
salesOffices.listen("B", function (price, area) { console.log("B 价格", price); console.log("B 面积", area); });
salesOffices.trigger("A", 100); salesOffices.trigger("B", 100, 500);
|
具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| var salesOffices = {}; salesOffices.clientList = {};
salesOffices.listen = function (key, fn) { if (!this.clientList[key]) { this.clientList[key] = []; } this.clientList[key].push(fn); };
salesOffices.trigger = function () { let key = Array.prototype.shift.call(arguments); for (let i = 0; i < this.clientList[key].length; i++) { this.clientList[key][i].apply(this, arguments); } };
salesOffices.remove = function (key, fn) { if (this.clientList[key]) { let arr = this.clientList[key];
if (fn) { for (let i = 0; i < arr.length; i++) { if (arr[i].name === fn.name) { arr.splice(i, 1); } } } else { this.clientList[key] = []; } } };
function fn1(price) { console.log("A价格", price); }
salesOffices.listen("A", fn1);
salesOffices.listen("A", function () { console.log("第二个 A"); });
salesOffices.remove("A");
salesOffices.listen("B", function (price, area) { console.log("B 价格", price); console.log("B 面积", area); });
salesOffices.trigger("A", 100); salesOffices.trigger("B", 100, 500);
|
发布订阅的通用实现
在上一节中,我们只是实现了一个发布订阅的功能不够通用,所以我们将继续提取出一个通用的实现,这样我们就可以在不同的地方使用这个功能了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| class Observer { constructor() { this.subscribers = {}; }
listen(key, fn) { if (!this.subscribers[key]) { this.subscribers[key] = []; } this.subscribers[key].push(fn); } trigger() { const key = Array.prototype.shift.call(arguments); for (let i = 0; i < this.subscribers[key].length; i++) { this.subscribers[key][i].apply(this, arguments); } } remove(key, fn) { if (this.subscribers[key]) { let arr = this.subscribers[key]; if (fn) { for (let index = 0; index < arr.length; index++) { if (arr[index].name === fn.name) { this.subscribers[key].splice(index, 1); } } } else { this.subscribers[key] = []; } } } }
let observer = new Observer();
function fn(price) { console.log("fn() A", price); }
observer.listen("A", function (price) { console.log("A的价格", price); });
observer.listen("A", fn);
observer.listen("B", function (price) { console.log("B的价格", price); }); observer.remove("A", fn);
observer.trigger("A", 100);
observer.trigger("B", 500);
|
思维导图
参考文章
《JavaScript 设计模式与开发实践》
观察者模式 vs 发布订阅模式,千万不要再混淆了
理解【观察者模式】和【发布订阅】的区别
JavaScript:发布-订阅模式与观察者模式