观察者模式简介

观察者模式(Observer)又被称作发布-订阅模式消息机制,定义了一种依赖关系,解决了主体对象与观察者之间功能的耦合,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

发布-订阅模式可以广泛的用于异步编程中,这是一种替代传统回调函数的方案。比如,我们可以订阅ajax请求的error,success等事件,在请求出错或者成功的时候做一些逻辑处理。或者如果想在动画的每一帧完成之后做一些事情,那我们就可以订阅一个事件,然后在动画的每一帧完成之后发布这个事件。在异步编程中使用发布-订阅模式,我们无需过多的关注对象在异步运行期间的内部状态,而只需要订阅感兴趣的事件发生点。

发布-订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式的调用另一个对象的某个接口,此模式让两个对象松耦合的联系在一起,虽然不清楚彼此内部的细节,但这也不影响他们之间的相互通信。当有新的订阅者出现时,发布者的代码不需要做任何的修改;同样的,发布者需要改变时,也不影响之前的订阅者,只要之前约定的事件名称没有发生改变,就可以自由的改变他们。

创建观察者模式的过程

我们想要把观察者对象创建出来,首先我们需要一个用于存放消息的容器对象(也被称作“消息容器”)和三个方法,分别是订阅消息方法、取消订阅消息的方法、发送订阅消息的方法。

1
2
3
4
5
6
7
8
9
10
11
const Observer = (function () {
let eventList = {},
subscribe,
publish,
remove;
return {
subscribe,
publish,
remove
}
})()

此即为发布-订阅模式的雏形。接下来我们来一一实现这三个方法:

首先,订阅消息的方法subscribe,订阅消息,首先我们需要知道订阅得消息名称,所以分析得知需要一个参数来代表订阅的消息名称,然后订阅了这个消息,总得知道订阅了之后要做什么事情吧,所以,我们还需要一个回调函数作为第二个参数用来表明此消息发布时所要做的事情。由此,请看代码:

1
2
3
4
5
6
7
// 订阅事件
subscribe = function (key, fn) {
if (typeof eventList[key] == 'undefined') {
eventList[key] = []
}
eventList[key].push(fn)
}

接下来,来看取消订阅的方法remove,这个应该不难想象,第一个参数自然为要取消订阅的消息名称,第二个参数如果有的话,则为要取消订阅的方法,如果没有,则取消此消息的所有回调函数,请看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 取消订阅
remove = function (key, fn) {
if (!eventList[key]) {
return false
}
let fns = eventList[key]
if (!fn) {
fns.length = 0
return
}
for (let i = 0, len = fns.length; i < len; i++) {
if (fns[i] === fn) {
fns.splice(i, 1)
}
}
}

最后,我们来看最重要的发布消息的方法:对于发布消息的方法,其作用是当观察者发布一个消息时将所有的订阅者订阅的消息一次性执行。故消息名称的参数是必须的,否则不知道要发布哪个消息:

1
2
3
4
5
6
7
8
9
10
11
// 发布事件
publish = function (key) {
if (!eventList[key]) {
return false
}
let fns = eventList[key],
args = Array.prototype.slice.call(arguments, 1)
for (let i = 0; i < fns.length; i++) {
fns[i].apply(this, args)
}
}

至此,我们已经完成了发布-订阅模式的对象模型,让我们看一下完整的代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const Observer = (function () {
let eventList = {},
subscribe,
publish,
remove;
// 订阅事件
subscribe = function (key, fn) {
if (typeof eventList[key] == 'undefined') {
eventList[key] = []
}
eventList[key].push(fn)
}

// 发布事件
publish = function (key) {
if (!eventList[key]) {
return false
}
let fns = eventList[key],
args = Array.prototype.slice.call(arguments, 1)
for (let i = 0; i < fns.length; i++) {
fns[i].apply(this, args)
}
}

// 取消订阅
remove = function (key, fn) {
if (!eventList[key]) {
return false
}
let fns = eventList[key]
if (!fn) {
fns.length = 0
return
}
for (let i = 0, len = fns.length; i < len; i++) {
if (fns[i] === fn) {
fns.splice(i, 1)
}
}
}

return {
subscribe,
publish,
remove
}
})()

接下来让我们一起验证一下吧:

1
2
3
4
5
6
7
8
9
10
11
Observer.subscribe('food', function (num) {
console.log('我想吃香蕉', num)
})
Observer.subscribe('food', function (n, m) {
console.log('我想吃苹果', n, m)
})
Observer.subscribe('food', function (m) {
console.log('我想吃凤梨', m)
})

Observer.publish('food', 36, 37)

查看控制台打印结果:

可以看到,三个人分别先后订阅了food的消息,然后发布的时候大家订阅的消息回调也都得到了执行。