1. 定义
《JavaScript设计模式与开发实践》中对观察者模式的定义如下:
发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript 开发中,我们一般用事件模型来替代传统的发布—订阅模式。
2. 案例
Vue2
中有四个事件相关的 API
:
vm.$on
:监听当前实例上的自定义事件。vm.$once
:监听一个自定义事件,但是只触发一次。vm.$off
:移除自定义事件监听器。vm.$emit
:触发当前实例上的事件。
这四个 API
不能跨组件通信,下一节我们将其作为参考实现全局的发布-订阅模式。
3. 简单实现
发布—订阅模式可以用一个全局的 Event 对象来实现,订阅者不需要了解消息来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event 作为一个类似“中介者”的角色,把订阅者和发布者联系起来。见如下代码:
JS
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
const Events = (function() {
const events = {};
return {
on: on,
once: once,
off: off,
emit: emit
}
/**
* 监听
* @params {string | Array<string>} event 事件名或包含多个事件名的数组
* @params {Function} fn 回调函数
*/
function on(event, fn) {
if (!event) return;
if (Array.isArray(event)) {
for (let i = 0, len = event.length; i < len; i++) {
this.on(event[i], fn);
}
} else {
(events[event] || (events[event] = [])).push(fn);
}
}
/**
* 监听一次。一旦触发之后,监听器就会被移除。
* @params {string | Array<string>} event 事件名或包含多个事件名的数组
* @params {Function} fn 回调函数
*/
function once(event, fn) {
if (Array.isArray(event)) {
for (let i = 0, len = event.length; i < len; i++) {
this.once(event[i], fn);
}
} else {
const fnWrapper = function() {
// 注意这里取消监听的是 fnWrapper ,而不是 fn
this.off(event, fnWrapper);
fn.apply(this, arguments);
}
this.on(event, fnWrapper);
}
}
/**
* 取消监听
* @params {string | Array<string>} event 事件名或包含多个事件名的数组
* @params {Function} fn 取消监听的回调函数
*/
function off(event, fn) {
if (!event) return;
if (Array.isArray(event)) {
for (let i = 0, len = event.length; i < len; i++) {
this.off(event[i], fn);
}
} else {
if (!fn) return events[event] = [];
const eventsList = events[event];
for (let i = 0, len = eventsList.length; i < len; i++) {
if (eventsList[i] === fn) {
eventsList.splice(i, 1);
break;
}
}
}
}
/**
* 触发事件
* @params {string } event 触发的事件名
* @params {Array} [args] 剩余参数
*/
function emit(event) {
const eventsList = events[event] || [];
const args = Array.prototype.slice.call(arguments, 1); // 移除第一个参数
eventsList.forEach(ev => {
ev.apply(this, args);
});
}
})();