目录
- 前言
- 一、发布订阅模式
- 什么是发布订阅模式?
- 应用场景
- 二、观察者模式
- 1)什么是观察者模式?
- 2)应用场景
- 3)vue中的观察者模式
- 观察者(订阅者) - Watcher
- 目标者(发布者) - Dep
- 没有事件中心
- 三、发布订阅模式和观察者模式的区别
- 1)从组成分析
- 2)从关系上分析
- 3)从使用角度分析
前言
观察者模式和发布订阅模式作为日常开发中经常使用到的模式,我一直不能做到很好的区分。最近在看Vue的源码,里面设计到了观察者模式,比较感兴趣,就去学习了下,这里做个总结吧。
一、发布订阅模式
什么是发布订阅模式?
基于一个事件中心
,接收通知的对象是订阅者,需要先订阅某个事件,触发事件的对象是发布者,发布者通过触发事件,通知各个订阅者。
举例:比如平时订阅的微信公众号,这里就涉及到两个角色:公众号(事件中心)和订阅了公众号的用户(订阅者)。当公众号的作者发布了文章之后,订阅公众号的用户就会收到消息,这里又涉及到了一个角色:公众号的作者(发布者).
应用场景
vue中的事件总线就是使用的发布订阅模式;它使用 $emit
、$on
进行兄弟组件通信,进行参数传递。
手动实现vue中的发布订阅模式:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue 中发布订阅模式</title>
</head>
<body><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script> // Vue 的实例,创建事件总线let bus = new Vue()// 订阅事件bus.$on('eventName1', val => {console.log('eventName1---->', val)})bus.$on('eventName2', val => {console.log('eventName2---->', val)})// 发布事件bus.$emit('eventName1', 'eventName1')bus.$emit('eventName2', 'eventName2')}</script>
</body>
</html>
打印结果:
在上述代码中,bus
就是我们创建的 事件总线
,它是一个 Vue 实例。我们可以在不同的组件中引入这个实例,并使用 $on 方法来监听事件,使用 $emit 方法来触发事件。通过共享同一个事件总线实例,不同组件之间可以通过事件进行通信,实现解耦。
需要注意的是,使用 事件总线模式 时要确保在适当的时候 销毁
事件总线,以避免出现 内存泄漏
的问题。可以在组件的生命周期钩子中销毁事件总线:
beforeDestroy() {bus.$off();
}
二、观察者模式
1)什么是观察者模式?
目标者对象 和 观察者对象 有相互依赖的关系,观察者对某个对象的状态进行观察,如果对象的状态发生改变,就会通知所有依赖这个对象的观察者进行更新操作。
观察者模式相比发布订阅模式少了个 事件中心
。
- 目标者对象【Subject】:是被观察的对象,它维护一组观察者,并提供用于
添加、删除和通知
观察者的方法。 - 观察者对象【Observe】:接收 Subject 状态变更通知并处理。
- 目标者对象【Subject】状态变更时,通知所有观察者对象【Observe】进行更新操作。
2)应用场景
观察者模式在Vue中应用场景:数据响应式变化。
在上一篇 Vue源码学习 - 数据响应式原理 文章中已经了解到,每个响应式属性都有 一个 dep实例 ,dep存放了依赖这个属性的 watcher(watcher是观测数据变化的函数),如果数据发生变化,dep 就会通知所有依赖它的 观察者watcher 去调用更新方法。因此,观察者需要被目标对象收集,目的是通知依赖它的所有观察者。
为什么watcher中也要存放dep呢?原因是因为当前正在执行的 watcher 需要知道此时是哪个 dep 通知了自己。
3)vue中的观察者模式
观察者(订阅者) - Watcher
update()
: 当事件发生时,具体要做的事情。
目标者(发布者) - Dep
subs数组
:存储所有的观察者。addSub()
:添加观察者。removeSub()
:移除观察者。notify()
:通知观察者; 当事件发生后调用所有观察者的update()。
没有事件中心
手动实现观察者模式
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>观察者模式</title>
</head>
<body><script>// 目标者(发布者)class Dep {constructor () {// 记录所有的订阅者this.subs = []}// 添加订阅者addSub (sub) {if (sub && sub.update) {this.subs.push(sub)}}// 发布通知notify () {this.subs.forEach(sub => {sub.update()})}}// 观察者(订阅者)class Watcher {update () {console.log('update')}}//创建实例化对象 测试一下let dep = new Dep()let watcher = new Watcher()let watcher1 = new Watcher()// 添加订阅者dep.addSub(watcher)dep.addSub(watcher1)// 开启通知dep.notify()// 执行结果 update ---></script>
</body>
</html>
打印结果:触发两次更新通知。
三、发布订阅模式和观察者模式的区别
1)从组成分析
- 观察者模式里,只有两个角色:
观察者
和目标者
(也可以叫被观察者)。其中 被观察者 是重点。 - 发布订阅模式里,不仅仅只有
发布者
和订阅者
,还有一个事件中心
。其中 事件中心 是重点。
观察者模式 | 发布订阅模式 |
---|---|
2个角色 | 3个角色 |
重点是 被观察者(目标者) | 重点是 事件中心 |
2)从关系上分析
- 观察者和目标者,是松耦合的关系
- 发布者和订阅者,则完全不存在耦合
3)从使用角度分析
- 观察者模式,多用于 单个应用内部 (Vue中的数据响应式变化就是观察者模式)
- 发布订阅模式,更多用于 跨应用的模式(比如我们常用的
消息中间件
)
可参考:
发布订阅模式和观察者模式