javascript 设计模式之单例模式javascript
javascript 适配、代理、装饰者模式的比较github
javascript 设计模式之观察者模式promise
javascript 设计模式之发布订阅者模式markdown
上一篇讲到观察者模式,这篇要讲下发布-订阅模式。 有人可能会说了:这两个不是一回事吗?确实这两个模式的核心思想、运行机制上没有本质的差异。 但仍是有些差异,要否则我这篇要讲啥,且听我娓娓道来
基于一个主题/事件通道,但愿接收通知的对象(称为subscriber)经过自定义事件订阅主题,被激活事件的对象(称为publisher)经过发布主题事件的方式被通知。
还记得上次那个小明、小红、小二去楼盘买房的事?
小明最近看中了一个楼盘,到了售楼处以后才被告知,该楼盘的房子还未开盘,具体开盘时间还没定。除了小明,一块儿来的还有小红,小二人,为了第一时间得知楼盘开盘时间,他们都把电话号码留给了销售员王五,王五也答应楼盘一开盘就会发消息通知他们。 过了不久,新楼盘开盘时间定了,王五赶忙拿出花名册,遍历上面的号码,群发了短信通知了他们。 至于这些人收到通知是选择走路来、开车来、或者不来就自行决定了
这个是观察者模式。
但若是小明等人没有去售楼处找五五,而是去该楼盘官网查看,得知还没开盘关注了该楼盘进展。开盘以后,王五也不会群发短信,而是将消息同步到官网,这样官网自动通知了关注该楼盘的意向购房者。 这样就是发布订阅者模式。
重点概念对号入座
发布者: 王五
订阅者: 小明、小红,小二
事件中心: 官网
订阅事件: 小明、小红、小二进入官网关注楼盘进展
通知变化: 官网自动通知了关注楼盘的意向购买者
这二者有啥差异呢?
类比官网,事件中心应该要有发事件( emit ),监听事件( on ),还有销毁事件监听者( off ),销毁指定事件的全部监听者( offAll ),还有只触发一次( once )的功能
class EventEmitter {
constructor() {
// handleEvents 是一个 map ,用于存储事件与回调之间的对应关系
this.handleEvents = {}
}
/** * 注册事件监听者, 它接受事件类型和回调函数做为参数 * @param {String} type 事件类型 * @param {Function} callback 回调函数 */
on(type, callback) {
// 先检查一下事件类型有没有对应的监听函数队列
if (!this.handleEvents[type]) {
// 若是没有,那么首先初始化一个监听函数队列, 不然直接 push 会报错
this.handleEvents[type] = []
}
// 把回调函数推入事件类型的监听函数队列里去
this.handleEvents[type].push(callback)
}
/** * 发布事件,它接受事件类型和监听函数入参做为参数 * @param {String} type 事件类型 * @param {...any} args 参数列表,把emit传递的参数赋给回调函数 */
emit(type, ...args) {
// 检查事件类型是否有监听函数队列
if (this.handleEvents[type]) {
// 若是有,则逐个调用队列里的回调函数
this.handleEvents[type].forEach((callback) => {
callback(...args)
})
}
}
/** * 移除某个事件回调队列里的指定回调函数 * @param {String} type 事件类型 * @param {Function} callback 回调函数 */
off(type, callback) {
const callbacks = this.handleEvents[type]
const index = callbacks.indexOf(callback)
// 找到则移除
if (index !== -1) {
callbacks.splice(index, 1)
}
// 该事件类型对应的回调函数为空了,则将该对象删除
if (callbacks.length === 0) {
delete this.handleEvents[type]
}
}
/** * 移除某个事件的全部回调函数 * @param {String} type 事件类型 */
offAll(type) {
if (this.handleEvents[type]) {
delete this.handleEvents[type]
}
}
/** * 为事件注册单次监听器 * @param {String} type 事件类型 * @param {Function} callback 回调函数 */
once(type, callback) {
// 对回调函数进行包装,使其执行完毕自动被移除
const wrapper = (...args) => {
callback.apply(args)
this.off(type, wrapper)
}
this.on(type, wrapper)
}
}
复制代码
测试
// 建立事件管理器实例
const emitter = new EventEmitter()
// 注册一个refresh事件监听者
emitter.on('refresh', function () {
console.log('调用后台获取最新数据')
})
// 发布事件refresh
emitter.emit('refresh')
// 也能够emit传递参数
emitter.on('refresh', function (pageNo, pageSize) {
console.log(`调用后台获取参数,参数为:{pageNo:${pageNo},pageSize:${pageSize}}`)
})
// 此时会打印两条信息,由于前面注册了两个refresh事件的监听者
emitter.emit('refresh', '2', '20')
// 测试移除事件监听
const toBeRemovedListener = function () {
console.log('我是一个能够被移除的监听者')
}
emitter.on('testRemove', toBeRemovedListener)
emitter.emit('testRemove')
emitter.off('testRemove', toBeRemovedListener)
// 此时事件监听已经被移除,不会再有console.log打印出来了
emitter.emit('testRemove')
// 测试移除refresh的全部事件监听
emitter.offAll('refresh')
// 此时能够看到emitter.handleEvents已经变成空对象了,再emit发送refresh事件也不会有反应了
console.log(emitter)
emitter.once('onlyOne', function () {
console.info('只会触发一次')
})
emitter.emit('onlyOne')
// 不会弹出信息
emitter.emit('onlyOne')
复制代码
// 订阅这些函数,当点击时触发发布,会依次触发这些函数
$('#btn1').click(function () {
alert(1)
})
$('#btn1').click(function () {
alert(2)
})
$('#btn1').click(function () {
alert(3)
})
复制代码
promise 就是观察者模式的经典场景: 先将 then 里面的函数储存起来,在 resovle 与 reject 里取出函数,并执行函数。 其实就是收集依赖->触发通知->取出依赖执行的方式 具体能够看手写 promise
// 自定义事件,自定义回调
var callbacks = $.Callbacks() // 注意大小写, C 为大写
callbacks.add(function (info) {
console.info('fn1', info)
})
callbacks.add(function (info) {
console.info('fn2', info)
})
callbacks.add(function (info) {
console.info('fn2', info)
})
callbacks.fire('come')
callbacks.fire('go')
复制代码
const EventEmitter = require('events').EventEmitter
const emitter1 = new EventEmitter()
emitter1.on('some', (name) => {
// 监听 some 事件
console.info('some event is occured 1 ' + name)
})
emitter1.on('some', () => {
// 监听 some 事件
console.info('some event is occured 2')
})
// 触发 some 事件
// 并传递参数
emitter1.emit('some', 'zhangsan')
复制代码
观察者模式:
发布订阅者模式:
看的出来,发布订阅者模式更加高级,由于它更加松散,没有耦合,那是否是现实中发布订阅者模式用的更多呢?实际上不是的。由于在实际开发中,咱们的模块解耦诉求并非要求它们彻底解耦,若是两个模块之间自己存在关联,且这种关联是稳定的、必要的,那这时就应该用观察者模式便可。
你的点赞是对我最大的确定,若是以为有帮助,请留下你的赞扬,谢谢!!!