JS设计模式-观察者模式
我个人觉得观察者模式(订阅-发布模式)是最常会见到的一种设计模式了,即是没了解过设计模式,你应该也见过:
window.addEventListener('click', function() {
// TODO
})
又或者是在VUE开发中父子组件使用$emit
和$on
进行事件通信,以及不相关组件的eventbus
通信。这都是比较常见的观察者模式。
让我们来试想这样一种情况:
function fnA() {
console.log('function A');
}
function fnB() {
fnA()
console.log('function B');
}
function fnC() {
fnB()
console.log('function C');
}
C调用了B,B又调用了A,如此强的依赖性会导致这其中有一个地方出了问题,都会导致后续的函数异常报错。
还有一种情况就是上面提到的跨文件、跨组件的事件传递,为此我们来简单设计一个观察者,我写的是极简版的,缺少很多判断及验证,如果你想看更详细的,建议来参考VUE的源码,大概在==54==行开始。
首先创建一个观察者对象,在开发中一般是放在全局的js文件中的。
const event = {
// 存放发布的事件
eventList: {},
// 发布,为了方便理解,这里我默认同一个key下只能有一个事件
$emit: function (key, fn) {
Reflect.set(this.eventList, key, fn)
},
// 订阅,有key就执行,否则返回false
$on: function (key, ...arguments) {
return Reflect.has(this.eventList, key) ? this.eventList[key](...arguments) : false
}
}
上面的例子稍微优化一下:
function fnA() {
console.log('function A');
}
event.$emit('fnB', function() {
fnA()
console.log('function B');
})
function fnC() {
event.$on('fnB')
console.log('function C');
}
乍一看似乎没什么变化,但是却降低了三个函数之间的耦合,在fnC
中,它只需要订阅执行时间就可以了,无论是fnA
被删除了,还是fnB
中出现异常报错了,都不会影响fnC
的执行了。
甚至可以里用观察者对象来进行跨文件、组件的事件传递
在A文件中发布:
// 发布
event.$emit('test', function (name, sex) {
console.log(`${name}: ${sex}`);
})
可以在B文件中订阅事件:
// 订阅
event.$on('test', '张三', '男') // 张三:男
可以发现,这种设计模式的最大好处就解耦合和跨文件的事件传递,但是它也有个问题,就是把函数之间的关系隐藏的更深了,维护起来追踪BUG可能会变得复杂。
各位可以再完善一下订阅对象,例如window.addEventListener
添加十个同名事件,在订阅的时候会十个事件全部执行,另外还有window.removeEventListener
来取消订阅。甚至还可以添加命名空间来让订阅对象各取所需。