发布订阅模式
#
前言很久前想深入理解Vue的底层原理,看了很多博客,只知道是“数据劫持+发布订阅模式”,之前写了一篇博客,懂了数据劫持,最近看到很多源码分析mvvm框架,说了很多次“发布订阅模式”,花了一下午加一晚上终于算是弄懂了!(前期的很多积累很重要,没有基础的支撑,感觉花再多时间啃某个知识点也是浪费时间,因为可能一个点不懂,一连串都会断掉,体会不到设计的巧妙)
#
正题本文将从以下四个方面介绍
- 发布订阅模式的作用是什么
- 发布订阅模式详解
- 实现发布订阅模式
- 发布订阅模式与观察者模式的区别
#
1. 发布订阅模式的作用是什么两个字——解耦。
换一个比较高大上的说法:定义了一个一对多的依赖关系,当有关状态发生变更时,则执行相应的更新。
实际上,设计模式是为了更好的维护代码。在实现功能方面,把代码全都写在一起是完全没问题的,但是这样的代码一般称之为“屎山”。每更新一个需求,可能就在原来某个判断条件内写一大堆逻辑。极其不利于后期的维护。而发布订阅的作用,就是为了优雅的解决这种需求的,一种设计模式。
#
2. 发布订阅模式详解发布订阅模式需要哪几个步骤?
- 添加订阅
- 取消订阅
- 发布订阅
对于订阅,需要两个参数:一个是订阅的主题,一个是订阅内容发布后需要进行处理的操作。
设想这么一个场景:我要用洗衣机洗衣服,有白色的衣服和黑色的衣服,和一个盆。首先,把盆里白色的衣服放到洗衣机里,白色的衣服有外套,衬衫,将白衣服一件一件的放到洗衣机里,然后打开洗衣机。以此类推处理黑色的衣服。
对上述场景分析,带入到发布订阅模式中:
- 主题: 白色衣服、黑色衣服
- 操作: 洗衣服
- 添加订阅: 将某个颜色的某件衣服放到盆里
- 取消订阅: 将某个颜色的所有衣服从盆里取出来
- 发布订阅: 将盆里的某种颜色的所有衣服,放到洗衣机里洗
#
3. 实现发布订阅模式话不多说,第二步的分析,直接上代码:
function subPub() { this.list = {}; //这就是那个盆}
subPub.prototype = { constructor: subPub, subscribe(key, fn) { //订阅 if(!this.list[key]) { //如果之前没有这个类别,新增一个 this.list[key] = []; } this.list[key].push(fn); //将事件触发后需要进行的操作放进去 },
unsubscribe(key) { //取消订阅 if(key in this.list) { delete this.list[key]; //删除这个属性,即取消所有内容,将白色衣服全都取出来 } },
publish(key) { //发布某个主题的订阅内容 if(!this.list[key]) { return } var args = Array.prototype.slice.call(arguments, 1); //将传入的参数取下来 var fns = this.list[key]; //将某个主题的全部内容取出 for(let i=0; i<fns.length; i++) { fns[i].apply(this, args); //this还是用当前调度中心的this,不然参数都传不过去 } }
}
function fn1() { console.log("我是白衬衫,我被洗了,啊啊啊");}function fn2() { console.log("我是白裤子,我被洗了,啊啊啊");}function fn3() { console.log("我是黑黑黑衬衫,我被洗了,啊啊啊");}
var sub = new subPub(); //新建发布订阅对象
sub.subscribe("wash_white", fn1); //将白衬衫放到盆里sub.subscribe("wash_white", fn2); //将白裤子放到盆里sub.subscribe("wash_black", fn3); //将黑衬衫放到盆里
sub.publish("wash_white"); //发布订阅的内容,执行相应的操作。 打开洗衣机洗所有的白衣服//我是白衬衫,我被洗了,啊啊啊//我是白裤子,我被洗了,啊啊啊
sub.publish("wash_black"); //洗所有的黑衣服//我是黑黑黑衬衫,我被洗了,啊啊啊
//也可以取消订阅sub.unsubscribe("wash_white"); //白衬衫今天约会还要穿,先不洗了吧sub.publish("wash_white"); //洗所有的白衣服 什么都不会发生
sub.publish("wash_black"); //黑衣服没事//我是黑黑黑衬衫,我被洗了,啊啊啊
//也可以在发布主题的时候传参function fn4(args) { console.log("这个参数是发布的时候传送过来的:--->"+ args);}
sub.subscribe("test_args", fn4);sub.publish("test_args", "我是从发布订阅内部传过来的参数!!");//这个参数是发布的时候传送过来的:--->我是从发布订阅内部传过来的参数!!
以上代码,在Chrome控制台亲测有效。
#
4. 发布订阅模式与观察者模式的区别看了很多说观察者模式和发布订阅模式是一样的,我觉得,其设计思想是一样的,实际上还是有些许差别。
先上观察者模式代码:
function Subject() { //所观察的主题 this.observers = []; //这里只是一个数组,只能有一个主题}
Subject.prototype = { constructor: Subject, add(ob) { //添加观察者对象 this.observers.push(ob); },
remove(ob) { //删除观察者对象 var obs = this.observers; for(let i=0; i<obs.length; i++) { if(ob===obs[i]) { this.observers.splice(i, 1); break; } } },
notify() { //发布消息 // var args = Array.prototype.slice.call(arguments); var obs = this.observers; for(let i=0; i<obs.length; i++) { obs[i].update(); } }}
function Observer(name) { //观察者对象 this.name = name;}
Observer.prototype = { constructor: Observer, update() { //接受到notify后,更新的函数 console.log(this.name); }}
var sub = new Subject(); //新建主题var ob1 = new Observer("lrq"); //新建观察者var ob2 = new Observer("lrq2");var ob3 = new Observer("lrq3");sub.add(ob1); //主题添加观察者sub.add(ob2);sub.add(ob3);sub.notify(); //发送消息// lrq// lrq2// lrq3
//也可以取消监听setTimeout(()=>{ sub.remove(ob2); //第二个观察者不看了 sub.notify();}, 3000);//lrq//lrq3
以上就是观察者模式的代码,说一下我对两种模式的理解:
- 发布订阅比观察者更灵活一点,发布订阅可以定义多个主题,而观察者就相当于只有一个主题
- 发布订阅的执行函数是自己随便写的,观察者模式的执行函数是要定义一个Observer类,在里面封装相应的update方法,创建每个实例时传入不同的参数。
等到写的bug更多的时候,看问题可能会更透彻吧,现在只是基本会用了这个设计模式。
以上。
written by LRQ
2019.12.5