很多文章都把 发布订阅模式
和 观察者模式
区别开来,从根本来说是同一种设计模式。抛开代码,假设我们订阅某个频道的咨询,一旦有新资讯发布,就能立即收到通知。我们只需要订阅某个事件即可。
我们经常使用的 DOM 事件,使用的就是发布订阅模式,我们逐步解析:
摘录来自: 曾探. “JavaScript设计模式与开发实践 (图灵原创)。
document.body.addEventListener( 'click', function(){
alert(2);
}, false );
document.body.click(); // 模拟用户点击”
如上代码,我们订阅 body 上面的点击事件,并告知事件中心要执行一个回调函数。当触发点击事件的时候,事件中心会触发我们注册的回调函数。
回想 node 的发布订阅模式,有一个事件中心 EventEmitter:
var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();
event.on('hello', function() {
console.log('hello 大家好');
});
setTimeout(function() {
event.emit('hello');
}, 1000);
上述代码 通过 event.on 注册了 hello
事件,一秒后又触发了这个事件,并执行了事件对应的回调。参照上述例子,我们 使用 on 方法注册监听,emit 方法触发监听,Let’s go:
class MyEvent {
// 存储所有注册的事件
eventObj = {}
/**
* 注册回调
* @param {*} type 事件类型
* @param {*} cb 回调函数
*/
on(type, cb) {
// 如果注册类型不存在,初始化为一个数组
if (!this.eventObj[type]) {
this.eventObj[type] = []
}
// 推入数组
this.eventObj[type].push(cb)
}
/**
* 触发订阅事件
* @param {*} type 事件类型
*/
emit(type, ...args) {
// 触发所有注册的事件
this.eventObj[type] && this.eventObj[type].forEach(cb => cb.apply(this, args))
}
}
const eventCenter = new MyEvent()
// 订阅事件类型
eventCenter.on('hello', () => {
console.log('hello 事件被触发');
})
eventCenter.on('hello', () => {
console.log('hello 事件第二次被触发');
})
setTimeout(() => {
// 触发事件
eventCenter.emit('hello')
}, 1000)
上述代码是可以跑通的,你可以试着注册不同的事件验证效果。
如果你熟练用过发布订阅模式的库,你会发现除了订阅还能取消订阅,一般是 off
方法,下面我们来完善这个方法。
/**
* 取消订阅
* @param {*} type 事件类型
* @param {*} cb 回调函数 可选
*/
off(type, cb) {
// 如果没有传入指定的函数,代表取消所有监听
const fns = this.eventObj[type]
if (!fns) return false
if (!cb) {
this.eventObj[type] = []
} else {
return fns.some((fn, i) => {
if (fn === cb) {
fns.splice(i, 1)
return true
}
})
}
}
}
我们验证是否成功:
// 订阅事件类型
eventCenter.on('hello', () => {
console.log('hello 事件被触发');
})
const secondFn = () => {
console.log('hello 事件第二次被触发');
}
eventCenter.on('hello', secondFn)
setTimeout(() => {
eventCenter.off('hello', secondFn)
// 触发事件
eventCenter.emit('hello')
}, 1000)
能看到我们赋予了匿名的箭头函数一个变量引用 secondFn
,这对应了 off
函数里面第二个参数,只要订阅的函数和取消订阅的函数属于同一个引用,就能取消订阅。如果订阅的是一个匿名函数,是无法单独取消的,因为取消订阅的时候无法拿到匿名函数的引用,只能 eventCenter.on('hello')
取消 hello
订阅事件的所有订阅。