发布-订阅模式(观察者模式)
定义
其定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变的时候,所有依赖它的对象都会得到通知。
主要解决
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
在JavaScript中,其主要有以下两个用处:
- 可以广泛应用于异步编程之中,这是一种代替回调函数的方案。
- 发布-订阅模式可以取代对象之间的硬编码通知机制,一个对象不用再显示地调用另外一个对象的某个接口。
实现
在web开发中,我们其实已经用到了发布-订阅模式,其就是DOM事件。
DOM事件
例如我们给一个按钮绑定一个点击事件:
1 | function click(){ |
实际上这个事件绑定的过程也是一个发布订阅模式。我们预先将依赖添加到发布者,当发布者事件变化,即点击事件发生的时候,我们再触发该依赖。这就是一个发布订阅模式的触发过程。
简单实现
除了DOM事件,我们还会经常实现一些自定义的事件,这种依靠自定义事件完成的发布-订阅者模式可以用于任何JavaScript代码中。
首先我们要明确三个部分
- 指定发布者。
- 为发布者添加一个 缓存列表用于缓存回调函数,以用于通知订阅者。
- 最后发布消息的时候,发布者遍历整个缓存列表,依次触发里面存放的回调函数。
下面来进行简单的开发
1 | let publisher = {} //发布者 |
发布订阅者+关键字
在上面的例子中,虽然能够实现发布订阅这个功能,但是还有一个问题是不同的订阅者可能需要订阅对不同的消息,所以我们需要给消息加上一个key
,以表示消息的类型。
实际上只是需要将消息队列定义为对象,以表示不同的key
;在发布消息时,按照key
来触发消息。
1 |
|
为任何对象添加发布订阅者
实际上发布者订阅者模式可以为任何对象添加,我们只需要将主要的三个属性添加到对象,即可使对象拥有发布订阅模式。
1 | let installEvent = function(obj){ |
取消订阅的事件
有时候我们需要取消订阅的事件,现在我们来实现这个功能。
1 | let remove = function(key, fn){ |
全局的发布-订阅对象
对于每一个需要实现发布订阅模式的对象,都需要在对象上添加相同的四个属性,虽然理论上没有性能损失不大,但是我们还是可以建立一个全局的发布订阅对象。
1 | let Event = (function(){ |
全局事件的问题
全局事件虽然可以解决开销,但是却出现了其他问题:命名冲突。
越来越多的发布订阅的添加,极其可能出现命名冲突的问题,由此我们如果使用全局事件,则必须使用命名空间来解决问题。
关于离线事件
在异步事件中,极可能出现我们添加监订阅不够及时,使得事件已经触发了。这样就会出现预期之外的错误了。所以在这种需求中,我们需要实现离线事件。此种情况,我们可以建立一个离线事件的堆栈,当发布的时候还没有对象来订阅此事件,则暂时将其存放至离线栈,等有对象来订阅此对象的时候,我们将遍历堆栈并且依次执行这些函数。
实现
下面我们将上面两点结合起来。
1 | let Event = (function(){ |
JavaScript中的发布订阅模式
值得注意的是,之前我们编写的发布订阅模式,和一些其他的语言(比如Java)中的实现还是有区别的。在Java中实现一个自己的发布订阅模式,通常会把订阅者自身当作引用传入发布者对象中,同时订阅者对象还需提供一个名为诸如update的方法,供发布者对象在适合的时候调用。而在JavaScript中,我们用注册回调函数的形式来代替传统的发布订阅模式,更加方便。
参考
- 《JavaScript设计模式与开发实践》