学习和总结文章同步发布于 https://github.com/xianshanna...,有兴趣可以关注一下,一起学习和进步。
首先我们需要了解两者的定义和实现的方式,才能更好的区分两者的不同点。
或许以前认为订阅发布模式是观察者模式的一种别称,但是发展至今,概念已经有了不少区别。
订阅发布模式
在软件架构中,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
或许你用过 eventemitter
、node 的 events
、Backbone 的 events
等等,这些都是前端早期,比较流行的数据流通信方式,即订阅发布模式。
从字面意思来看,我们需要首先订阅,发布者发布消息后才会收到发布的消息。不过我们还需要一个中间者来协调,从事件角度来说,这个中间者就是事件中心,协调发布者和订阅者直接的消息通信。
完成订阅发布整个流程需要三个角色:
- 发布者
- 事件中心
- 订阅者
以事件为例,简单流程如下:
发布者->事件中心<=>订阅者,订阅者需要向事件中心订阅指定的事件 -> 发布者向事件中心发布指定事件内容 -> 事件中心通知订阅者 -> 订阅者收到消息(可能是多个订阅者),到此完成了一次订阅发布的流程。
简单的代码实现如下:
class Event {
constructor() {
// 所有 eventType 监听器回调函数(数组)
this.listeners = {}
}
/**
* 订阅事件
* @param {String} eventType 事件类型
* @param {Function} listener 订阅后发布动作触发的回调函数,参数为发布的数据
*/
on(eventType, listener) {
if (!this.listeners[eventType]) {
this.listeners[eventType] = []
}
this.listeners[eventType].push(listener)
}
/**
* 发布事件
* @param {String} eventType 事件类型
* @param {Any} data 发布的内容
*/
emit(eventType, data) {
const callbacks = this.listeners[eventType]
if (callbacks) {
callbacks.forEach((c) => {
c(data)
})
}
}
}
const event = new Event()
event.on('open', (data) => {
console.log(data)
})
event.emit('open', { open: true })
Event 可以理解为事件中心,提供了订阅和发布功能。
订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。
观察者模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
观察者模式我们可能比较熟悉的场景就是响应式数据,如 Vue 的响应式、Mbox 的响应式。
观察者模式有完成整个流程需要两个角色:
- 目标
- 观察者
简单流程如下:
目标<=>观察者,观察者观察目标(监听目标)-> 目标发生变化-> 目标主动通知观察者。
简单的代码实现如下:
/**
* 观察监听一个对象成员的变化
* @param {Object} obj 观察的对象
* @param {String} targetVariable 观察的对象成员
* @param {Function} callback 目标变化触发的回调
*/
function observer(obj, targetVariable, callback) {
if (!obj.data) {
obj.data = {}
}
Object.defineProperty(obj, targetVariable, {
get() {
return this.data[targetVariable]
},
set(val) {
this.data[targetVariable] = val
// 目标主动通知观察者
callback && callback(val)
},
})
if (obj.data[targetVariable]) {
callback && callback(obj.data[targetVariable])
}
}
可运行例子如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover"
/>
<title></title>
</head>
<body>
<div id="app">
<div id="dom-one"></div>
<br />
<div id="dom-two"></div>
<br />
<button id="btn">改变</button>
</div>
<script>
/**
* 观察监听一个对象成员的变化
* @param {Object} obj 观察的对象
* @param {String} targetVariable 观察的对象成员
* @param {Function} callback 目标变化触发的回调
*/
function observer(obj, targetVariable, callback) {
if (!obj.data) {
obj.data = {}
}
Object.defineProperty(obj, targetVariable, {
get() {
return this.data[targetVariable]
},
set(val) {
this.data[targetVariable] = val
// 目标主动通知观察者
callback && callback(val)
},
})
if (obj.data[targetVariable]) {
callback && callback(obj.data[targetVariable])
}
}
const obj = {
data: { description: '原始值' },
}
observer(obj, 'description', value => {
document.querySelector('#dom-one').innerHTML = value
document.querySelector('#dom-two').innerHTML = value
})
btn.onclick = () => {
obj.description = '改变了'
}
</script>
</body>
</html>
两者的区别在哪?
角色角度来看,订阅发布模式需要三种角色,发布者、事件中心和订阅者。二观察者模式需要两种角色,目标和观察者,无事件中心负责通信。
从耦合度上来看,订阅发布模式是一个事件中心调度模式,订阅者和发布者是没有直接关联的,通过事件中心进行关联,两者是解耦的。而观察者模式中目标和观察者是直接关联的,耦合在一起(有些观念说观察者是解耦,解耦的是业务代码,不是目标和观察者本身)。
两者的优缺点?
优缺点都是从前端角度来看的。
订阅发布模式优点
- 灵活
由于订阅发布模式的发布者和订阅者是解耦的,只要引入订阅发布模式的事件中心,无论在何处都可以发布订阅。同时订阅发布者相互之间不影响。
订阅发布模式在使用不当的情况下,容易造成数据流混乱,所以才有了 React 提出的单项数据流思想,就是为了解决数据流混乱的问题。
订阅发布模式缺点
- 容易导致代码不好维护
灵活是有点,同时也是缺点,使用不当就会造成数据流混乱,导致代码不好维护。
- 性能消耗更大
订阅发布模式需要维护事件列队,订阅的事件越多,内存消耗越大。
观察者模式优点
- 响应式
目标变化就会通知观察者,这是观察者最大的有点,也是因为这个优点,观察者模式在前端才会这么出名。
观察者模式缺点
- 不灵活
相比订阅发布模式,由于目标和观察者是耦合在一起的,所以观察者模式需要同时引入目标和观察者才能达到响应式的效果。而订阅发布模式只需要引入事件中心,订阅者和发布者可以不再一处。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。