什么是观察者模式(Observer)?
观察者模式与发布订阅相比,耦合度更高,观察者模式中只有两个主体 目标对象 【Subject】和观察者【Observer】
观察者模式在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变的时候通知他们。 -- 维基百科
代码实现
1. 声明观察者Observer
class Observer {
constructor (name) {
this.name = name
}
// 观察者默认提供一个update方法,用于更新数据(业务逻辑代码)
update (info, ...args) {
console.log(`hello, ${this.name}, 告诉你一件事:${info}`)
}
}
2. 声明目标对象Subject
class Subject {
constructor () {
this.observerList = []
}
addObserver (observer) {
this.observerList.push(observer)
}
notify () {
this.observerList.forEach(observer => observer.update(...arguments))
}
}
整体代码实现
class Observer {
constructor (name) {
this.name = name
}
update (info, ...args) {
console.log(`hello, ${this.name}, 告诉你一件事:${info}`)
}
}
class Subject {
constructor () {
this.observerList = []
}
addObserver (observer) {
this.observerList.push(observer)
}
notify () {
this.observerList.forEach(observer => observer.update(...arguments))
}
}
const Jay = new Observer('jay')
const James = new Observer('james')
const PublicSub = new Subject()
PublicSub.addObserver(Jay)
PublicSub.addObserver(James)
PublicSub.notify('海贼王更新了')
PublicSub.notify('上海复工了')
/**
* 打印结果:
* hello, jay, 告诉你一件事:海贼王更新了
* hello, james, 告诉你一件事:海贼王更新了
* hello, jay, 告诉你一件事:上海复工了
* hello, james, 告诉你一件事:上海复工了
*/
观察者模式当对象更新时,会依次通知所有的观察者。
什么是发布订阅模式(Publisher & Subscriber)?
参考书籍-JavaScript设计模式与开发实践
举个例子
小明、小红都看上了某楼盘的房子,但是目前没有房源,小明、小红没有办法每天去售楼处询问是否有房源,于是,小明、小红将手机号留给了房产销售,某一天有房源了,销售依次给小明、小红打电话通知他们有房源了。
这种订阅房源信息的方式,就是发布-订阅模式。
注意:
- 以上的方式售楼处与购房者解耦,不再强耦合在一起
- 当有新的购房者出现,只需要把手机号留给售楼处(订阅)就行,售楼处并不关心购房者是男是女,还是一只猴子
- 售楼处的变化也不会影响购房者,只要售楼处记得通知购房者有新房源即可(发布)
释义
发布-订阅模式又叫观察者模式 它定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
特点:
- 发布-订阅模式广泛用于异步编程中,可以替代回调函数的方案
- 发布-订阅模式取代对象之间硬编码的通知机制,一个对象不再显式调用另一个对象的api。发布者、订阅者解耦,各自的变化并不会影响对方。
注意:多个订阅者订阅多个事件
代码实现
1. 订阅事件
class Pub {
constructor () {
/**
* events[key] = []
* 每个类型对应的订阅事件维护在数组中
*/
this.events = {}
}
// 订阅
listen (key, fn) {
if (!this.events[key]) {
this.events[key] = []
}
this.events[key].push(fn) // 新增订阅事件
}
}
2. 发布事件
// 发布
trigger (key, ...args) {
const fns = this.events[key]
if (!fns || fns.length === 0) { // 无订阅事件
return false
}
for (let i = 0, fn = null; fn = fns[i++];) {
fn.apply(this, args)
}
}
3. 取消订阅
// 取消订阅
removeListener (key, fn) {
if (this.events[key]) {
const fnIndex = this.events[key].findIndex(e => e === fn)
if (fnIndex !== -1) {
this.events[key].splice(fnIndex, 1)
}
}
if (this.events[key].length === 0) {
delete this.events[key]
}
}
// 取消所有订阅
removeAllListener (key) {
if (this.events[key]) {
delete this.events[key]
}
}
整体代码实现
class Pub {
constructor () {
this.events = {}
}
// 订阅
listen (key, fn) {
if (!this.events[key]) {
this.events[key] = []
}
this.events[key].push(fn) // 新增订阅事件
}
// 发布
trigger (key, ...args) {
const fns = this.events[key]
if (!fns || fns.length === 0) { // 无订阅事件
return false
}
for (let i = 0, fn = null; fn = fns[i++];) {
fn.apply(this, args)
}
}
// 取消订阅
removeListener (key, fn) {
if (this.events[key]) {
const fnIndex = this.events[key].findIndex(e => e === fn)
if (fnIndex !== -1) {
this.events[key].splice(fnIndex, 1)
}
}
if (this.events[key].length === 0) {
delete this.events[key]
}
}
// 取消所有订阅
removeAllListener (key) {
if (this.events[key]) {
delete this.events[key]
}
}
}
const publish = new Pub()
// jack和jay都订阅88w的房子
const Jack_sub_88 = city => {
console.log('新出88w楼盘,位置在' + city + ',通知jack')
}
const Jay_sub_88 = city => {
console.log('新出88w楼盘,位置在' + city + ',通知jay')
}
publish.listen('price88', Jack_sub_88)
publish.listen('price88', Jay_sub_88)
// james订阅100w的房子
const James_sub_100 = city => {
console.log('新出100w楼盘,位置在' + city + ',通知james')
}
publish.listen('price100', James_sub_100)
publish.trigger('price88', '南京') // 南京发布了一套88w的房子, 通知jack和jay
publish.trigger('price88', '苏州') // 苏州发布了一套88w的房子,通知jack和jay
publish.trigger('price100', '上海') // 上海发布了一套100w的房子,通知james
publish.removeListener('price88', Jack_sub_88) // jack已购房,取消订阅
publish.trigger('price88', '南通') // 南通发布了一套88w的房子,通知jay
观察者模式与发布订阅模式有什么区别?
| 设计模式 | 观察者模式 | 发布订阅模式 |
|---|---|---|
| 主体 | Object观察者、Subject目标对象 | Publisher发布者、Event Channel事件中心、Subscribe订阅者 |
| 主体关系 | Subject中通过observerList记录ObServer | Publisher和Subscribe不想不知道对方,通过中介联系 |
| 优点 | 角色明确,Subject和Object要遵循约定的成员方法 | 松散耦合,灵活度高,通常应用在异步编程中 |
| 缺点 | 紧耦合 | 当事件类型变多时,会增加维护成本 |
| 使用案例 | 双向数据绑定 | 事件总线EventBus、vue.$emits等 |
使用案例
观察者模式
vue数据双向绑定原理(数据劫持)
数据劫持简单示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue-observer-simple</title>
</head>
<body>
<div>
<input type="text" id="text">
<p id="show"></p>
</div>
</body>
<script>
const observer = {}
Object.defineProperty(observer, 'text', {
get: function () {
return observer
},
set: function (newVal) {
document.getElementById('text').value = newVal
document.getElementById('show').innerHTML = newVal
}
})
document.addEventListener('keyup', function (e) {
observer.text = e.target.value
})
</script>
</html>
[警告]注意:vue中数据双向绑定的数据劫持部分(Observer)是观察者模式,但是整个双向绑定是发布-订阅模式
发布订阅模式
node.js中的EventEmitter
api 清单
- emitter.addListener(eventName, listener) / emitter.on(eventName, listener)
- emitter.off(eventName, listener) / emitter.removeListener(eventName, listener)
- emitter.emit(eventName, [...args])
- emitter.once(eventName, listener)
- emitter.removeAllListeners([eventName])
额外的api扩展:
- emitter.getMaxListeners()
- emitter.setMaxListeners(n) -- 设置最大绑定数,默认超过10个会警告
- emitter.prependListener(eventName, listener) / emitter.prependOnceListener(eventName, listener) -- 向前添加监听事件
调整发布订阅示例代码
- emitter.addListener(eventName, listener)
- emitter.emit(eventName, [...args])
- emitter.removeListener(eventName, listener)
- emitter.removeAllListeners([eventName])
class EventEmitter {
constructor () {
// 存放订阅事件
this._events = Object.create(null)
}
// 订阅事件
addListener (eventName, listener) {
if (!this._events[eventName]) {
this._events[eventName] = []
}
this._events[eventName].push(listener) // 新增订阅事件
}
// 发布
emit (eventName, ...args) {
const listeners = this._events[eventName]
if (!listeners || listeners.length === 0) { // 无订阅事件
return false
}
for (let i = 0, listener = null; listener = listeners[i++];) {
listener.apply(this, args)
}
}
// 取消订阅
removeListener (eventName, listener) {
if (this._events[eventName]) {
const listenerIndex = this._events[eventName].findIndex(e => e === listener)
if (listenerIndex !== -1) {
this._events[eventName].splice(listenerIndex, 1)
}
}
if (this._events[eventName].length === 0) {
delete this._events[eventName]
}
}
// 取消所有订阅
removeAllListener (eventName) {
if (this._events[eventName]) {
delete this._events[eventName]
}
}
}
const testEvent = new EventEmitter()
const event_1 = (name) => {
console.log(`触发了事件${name}`)
}
testEvent.addListener('event_1', event_1)
testEvent.emit('event_1', '事件1')
完成emitter.on(eventName, listener)和emitter.off(eventName, listener)
emitter.on 即 emitter.addListener,是emitter.addListener的别名简写
同样的
emitter.off 即 emitter.removeListener,是emitter.addListener的别名简写
- emitter.on(eventName, listener)
node.js源码:lib/events.js
/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
* @param {Function} listener
* @returns {EventEmitter}
*/
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
- emitter.off(eventName, listener)
node.js源码:lib/events.js
/**
* Removes the specified `listener` from the listeners array.
* @param {string | symbol} type
* @param {Function} listener
* @returns {EventEmitter}
*/
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
// ...
return this;
};
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
代码实现
class EventEmitter {
constructor () {
// 存放订阅事件
this._events = Object.create(null)
}
// 订阅事件
addListener (eventName, listener) {
if (!this._events[eventName]) {
this._events[eventName] = []
}
this._events[eventName].push(listener) // 新增订阅事件
}
// 订阅事件 -- addListener的简写
on (eventName, listener) {
this.addListener(eventName, listener)
}
// 发布
emit (eventName, ...args) {
// ...
}
// 取消订阅
removeListener (eventName, listener) {
// ...
}
// 取消订阅 - removeListener的简写
off (eventName, listener) {
this.removeListener(eventName, listener)
}
// 取消所有订阅
removeAllListener (eventName) {
// ...
}
}
完成emitter.once(eventName, listener)
- 将listener二次包装
- 先绑定,调用listener后删除
once相关代码:
once (eventName, listener) {
function _wrap () {
listener(...arguments)
this.removeListener(eventName, _wrap)
}
_wrap.listener = listener
this.on(eventName, _wrap)
}
全量代码:
class EventEmitter {
constructor () {
// 存放订阅事件
this._events = Object.create(null)
}
// 订阅事件
addListener (eventName, listener) {
if (!this._events[eventName]) {
this._events[eventName] = []
}
this._events[eventName].push(listener) // 新增订阅事件
}
// 订阅事件 -- addListener的简写
on (eventName, listener) {
this.addListener(eventName, listener)
}
once (eventName, listener) {
function _wrap () {
listener(...arguments)
this.removeListener(eventName, _wrap)
}
_wrap.listener = listener
this.on(eventName, _wrap)
}
// 发布
emit (eventName, ...args) {
const listeners = this._events[eventName]
if (!listeners || listeners.length === 0) { // 无订阅事件
return false
}
for (let i = 0, listener = null; listener = listeners[i++];) {
listener.apply(this, args)
}
}
// 取消订阅
removeListener (eventName, listener) {
if (this._events[eventName]) {
const listenerIndex = this._events[eventName].findIndex(e => e === listener)
if (listenerIndex !== -1) {
this._events[eventName].splice(listenerIndex, 1)
}
}
if (this._events[eventName].length === 0) {
delete this._events[eventName]
}
}
// 取消订阅 -- removeListener的简写
off (eventName, listener) {
this.removeListener(eventName, listener)
}
// 取消所有订阅
removeAllListener (eventName) {
if (this._events[eventName]) {
delete this._events[eventName]
}
}
}
const testEvent = new EventEmitter()
const event_1 = (name) => {
console.log(`触发了事件${name}`)
}
testEvent.once('event_1', event_1)
testEvent.emit('event_1', '事件1触发第一次')
testEvent.emit('event_1', '事件1触发第二次')
完成向前添加监听事件
- emitter.prependListener(eventName, listener)
- emitter.prependOnceListener(eventName, listener)
利用flag进行判断处理
if (flag) {
this._events[eventName].unshift(listener);
} else {
this._events[eventName].push(listener);
}
相关代码调整:
// 订阅事件
addListener (eventName, listener, flag) {
if (!this._events[eventName]) {
this._events[eventName] = []
}
if (flag) {
this._events[eventName].unshift(listener) // 向前新增订阅事件
} else {
this._events[eventName].push(listener) // 新增订阅事件
}
}
// 订阅事件 -- addListener的简写
on (eventName, listener, flag) {
this.addListener(eventName, listener, flag)
}
// 订阅事件 -- 只订阅一次
once (eventName, listener, flag) {
function _wrap () {
listener(...arguments)
this.removeListener(eventName, _wrap)
}
_wrap.listener = listener
this.on(eventName, _wrap, flag)
}
// 向前添加订阅事件
prependListener (eventName, listener) {
this.on(eventName, listener, true)
}
// 向前添加订阅事件 -- 只订阅一次
prependOnceListener (eventName, listener) {
this.once(eventName, listener, true)
}
添加最大监听数控制
- emitter.getMaxListeners()
- emitter.setMaxListeners(n) -- 设置最大绑定数,默认超过10个会警告
class EventEmitter {
constructor () {
// 存放订阅事件
this._events = Object.create(null)
// 最大监听数
this.defaultMaxListeners = 10
this._count = this.defaultMaxListeners
}
getMaxListeners () {
return this._count ? this._count : this.defaultMaxListeners
}
setMaxListeners (num) {
this._count = num
}
// 订阅事件
addListener (eventName, listener, flag) {
if (!this._events[eventName]) {
this._events[eventName] = []
}
if (flag) {
this._events[eventName].unshift(listener) // 向前新增订阅事件
} else {
this._events[eventName].push(listener) // 新增订阅事件
}
if (this._events[eventName].length >= this.getMaxListeners()) {
console.warn('添加的监听事件已超过最大监听数')
}
}
// ...
}
EventEmitter全量代码实现
class EventEmitter {
constructor () {
// 存放订阅事件
this._events = Object.create(null)
// 最大监听数
this.defaultMaxListeners = 10
this._count = this.defaultMaxListeners
}
getMaxListeners () {
return this._count ? this._count : this.defaultMaxListeners
}
setMaxListeners (num) {
this._count = num
}
// 订阅事件
addListener (eventName, listener, flag) {
if (!this._events[eventName]) {
this._events[eventName] = []
}
if (flag) {
this._events[eventName].unshift(listener) // 向前新增订阅事件
} else {
this._events[eventName].push(listener) // 新增订阅事件
}
if (this._events[eventName].length >= this.getMaxListeners()) {
console.warn('添加的监听事件已超过最大监听数')
}
}
// 订阅事件 -- addListener的简写
on (eventName, listener, flag) {
this.addListener(eventName, listener, flag)
}
// 订阅事件 -- 只订阅一次
once (eventName, listener, flag) {
function _wrap () {
listener(...arguments)
this.removeListener(eventName, _wrap)
}
_wrap.listener = listener
this.on(eventName, _wrap, flag)
}
// 向前添加订阅事件
prependListener (eventName, listener) {
this.on(eventName, listener, true)
}
// 向前添加订阅事件 -- 只订阅一次
prependOnceListener (eventName, listener) {
this.once(eventName, listener, true)
}
// 发布
emit (eventName, ...args) {
const listeners = this._events[eventName]
if (!listeners || listeners.length === 0) { // 无订阅事件
return false
}
for (let i = 0, listener = null; listener = listeners[i++];) {
listener.apply(this, args)
}
}
// 取消订阅
removeListener (eventName, listener) {
if (this._events[eventName]) {
const listenerIndex = this._events[eventName].findIndex(e => e === listener)
if (listenerIndex !== -1) {
this._events[eventName].splice(listenerIndex, 1)
}
}
if (this._events[eventName].length === 0) {
delete this._events[eventName]
}
}
// 取消订阅 -- removeListener的简写
off (eventName, listener) {
this.removeListener(eventName, listener)
}
// 取消所有订阅
removeAllListener (eventName) {
if (this._events[eventName]) {
delete this._events[eventName]
}
}
}
const testEvent = new EventEmitter()
const event_1 = (name) => {
console.log(`触发了事件${name}`)
}
testEvent.once('event_1', event_1)
testEvent.emit('event_1', '事件1触发第一次')
testEvent.emit('event_1', '事件1触发第二次')