订阅发布模式的介绍
发布订阅模式, 它定义了一种一对多的关系, 可以使多个观察者对象对一个主题对象进行监听, 当这个主题对象发生改变时, 依赖的所有对象都会被通知到.
在生活中我们常常遇到这样一种情况, 我们在使用新闻 APP 看新闻的时候, 每个人喜欢的新闻类型各不一样, 比如我喜欢 NBA, 但是我们总不可能一天 24 小时在手机上一遍又一遍的刷新, 我们就会去新闻频道中选择 NBA 专栏来收藏, 当勇士或者湖人有最新消息, 就会通知我们去观看.
当然从上面的场景中是一个典型的发布订阅模式, APP 的 NBA 专栏属于发布者, 像我一样广大爱好篮球的小伙伴梦就属于订阅者, 当一有最新的消息, 它们就会发布给我们.
实际用途
1. 在 jquery 中很多地方都有发布订阅的踪迹, 例如事件中 on 和 trigger 中封装的方法.
2. 尤大大的 vue, 中子父组件通信使用的 emit() 和 on() 方法, 使得组件得到解耦, 开发更加高效.
如何实现订阅发布模式
1, 首先想好谁是发布者 (比如上边的 APP 的 NBA 专栏就是发布者);
2, 然后给发布者添加一个缓存列表, 用于存放回调函数来通知订阅者 (比如上面的我们球迷爱好者收藏了 NBA 专栏, 相当于向发布者注入了通知我们的函数);
3, 最后就是发布消息, 发布者遍历这个缓存列表, 依次触发订阅的函数.
表捉急, 端起小板凳, 先看一下这个简单的发布订阅模式:
- let NBAcol={};// 自定义一个 NBA 专栏对象
- NBAcol.list=[];// 这里放一个列表用来缓存订阅者的回调函数
- NBAcol.on=function(fun){
- this.list.push(fun); // 把 fn 先存到列表中
- };
- // 发布事件
- NBAcol.emit=function(){
- this.list.forEach(cb => {
- cb.apply(this, arguments);
- });// 当发布的时候再把列表里存的函数依次执行
- };
- // 小明的订阅 NBA 专栏
- NBAcol.on(function(team){
- console.log("我订阅的球队是:"+team)
- })
- // 小李的订阅 NBA 专栏
- NBAcol.on(function(team){
- console.log("我订阅的球队是:"+team)
- })
- NBAcol.emit('湖人');
- NBAcol.emit('勇士');
- /*
- 我订阅的球队是: 湖人;
- 我订阅的球队是: 湖人;
- 我订阅的球队是: 勇士;
- 我订阅的球队是: 勇士;
- */
上面就实现了一个简单的订阅发布模式, 不过从打印结果来看, 有些尴尬, 因为其实, 小明只想订阅湖人, 小李要订阅勇士. 可是专栏都给他们推送了, 显然不太合理. 之所以出现这种情况是因为在执行 on 方法的时候将订阅函数列表中的函数依次都执行了. 所以我们要对代码进行改造, 我们可以先增加一个 key, 使订阅者只订阅自己感兴趣的消息.
- let NBAcol={};// 自定义一个 NBA 专栏对象
- NBAcol.list=[];// 这里放一个列表用来缓存订阅者的回调函数
- NBAcol.on=function(key,fun){
- // 如果还没有订阅过此类消息, 给该类消息创建一个缓存列表
- if(!this.list[key]){
- this.list[key]=[];
- }
- this.list[key].push(fun); // 把 fn 先存到列表中
- };
- // 发布事件
- NBAcol.emit=function(){
- let key=Array.prototype.shift.call(arguments);// 取出消息类型名称
- let funs=this.list[key];// 匹配对应的回调函数的结合
- if(!funs||funs.length===0){// 如果没有订阅过消息, 则 return;
- return;
- };
- funs.forEach(fun => {
- fun.apply(this, arguments);
- });// 当发布的时候再把列表里存的函数依次执行
- };
- // 小明的订阅 NBA 专栏
- NBAcol.on('xiaomin',function(team){
- console.log("我订阅的球队是:"+team)
- })
- // 小李的订阅 NBA 专栏
- NBAcol.on('xiaoli',function(team){
- console.log("我订阅的球队是:"+team)
- })
- NBAcol.emit('xiaomin','湖人');
- NBAcol.emit('xiaoli','勇士');
- /*
- 我订阅的球队是: 湖人;
- 我订阅的球队是: 勇士;
- */
这样子就可以啦, 这个订阅发布的核心功能已经体现了.
如何取消事件的订阅
比如上面的列子, 假如我们订阅了很多东西, 不喜欢的时候我们要取消订阅, 该怎么办呢? 看如下代码:
- NBAcol.remove=function(key, fun) {
- // 这回我们加入了取消订阅的方法
- let funs = this.list[key];
- // 如果缓存列表中没有函数, 返回 false
- if (!funs) return false;
- // 如果没有传对应函数的话
- // 就会将 key 值对应缓存列表中的函数都清空掉
- if (!fun) {
- funs && (funs.length = 0);
- } else {
- // 遍历缓存列表, 看看传入的 fun 与哪个函数相同
- // 如果相同就直接从缓存列表中删掉即可
- funs.forEach((cb, i) => {
- if (cb === fun) {
- funs.splice(i, 1);
- }
- });
- }
- }
- // 取消 dog 方法的订阅
- NBAcol.remove('xiaoli',function(team){
- console.log("我订阅的球队是:"+team)
- });
这样就可以取消订阅啦, 但是实际的开源代码中, 封装远比这要复杂, 比如要考虑订阅数量, 还有多模块订阅的封装等等, 所以在这里我们还得在实际的业务模块中详细考虑.
发布订阅模式的缺点:
当然一个任何一个东西都是有两面性的, 同样发布订阅模式存在以下问题:
1, 创建订阅者需要消耗一定的时间和内存.
2, 虽然可以弱化对象之间的联系, 如果过度使用的话, 反而使代码不好理解及代码不好维护等等.
来源: https://juejin.im/post/5b2e65fee51d4558df370532