发布-订阅模式.又称观察者模式(Observer).它定义了对象之间的一对一或一对多的依赖关系.当一个对象发生变化时,它会通知到依赖它的其他对象.

发布-订阅模式广泛用于异步编程者中,是一种代替传递回调函数的方案.前端最典型的例子就是:EventTarget.addEventListener()监听DOM事件.

发布-订阅模式可以取代对象之间的硬编码,让两个对象松耦合联系在一起.

发布订阅模式的缺陷:

  1. 会消耗一定的时间和内存.
  2. 过度使用会将对象之间的联系深埋在背后,导致程序难以跟踪维护和理解.

简单的 自定义监听模式

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
// 自定义 事件监听对象
const event = {
clientList: [],
// 绑定 监听
listen (key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 订阅的消息添加缓存列表
},
// 触发 监听
trigger () {
var key = Array.prototype.shift.call(arguments); // 删除参数数组中第一个值,它是监听方法名.
var fns = this.clientList[key];
if (!fns || fns.length === 0) return false; // 如果没有找到绑定的方法
for(let i = 0, fn; fn = fns[i++]) {
fn.apply(this, arguments); // arguments 调用trigger方法时传入的参数(除了第一个参数外其他传入的实参)
}
},
// 移除 监听
remove(key, fn) {
var fns = this.clientList[key];
if(!fns) return false; // 如果key对应的消息没有被人订阅,则消息直接返回 false

if(!fn) {
fns&&(fns.length = 0) // 如果没有传入具体的回调函数,则表示需要取消key对应消息的所有订阅
} else {
for(let l = fns.length -1; l >= 0; l--) { // 反向遍历订阅的回调函数列表
var _fn = fns[l];
if (_fn === fn) {
fns.splice(l, 1); // 删除订阅者的回调函数
}
}
}
}
}
// 创动态给对象增加 发布-订阅功能的方法
const installEvent = function (obj) {
for(let key of Object.keys(obj)) {
obj[key] = event[key]
}
}

全局 发布-订阅对象

全局的 发布-订阅对象需要考虑命名空间,在上面👆发布-订阅对象基础上,我们增加命名空间方法.

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
const Event = (function () {
var global = this,
Event,
_default = 'default';
Event = function () {
var _listen,
_trigger,
_remove,
_slice = Array.prototype.slice,
_unshift = Array.prototype.unshift,
nameSpaceCache = {},
_create,
find,
each = function (ary, fn) {
var ret;
for(let i = 0, l =ary.length; i < l; i++) {
var n = ary[i];
ret = fn.call(n, i , n);
}
return ret;
};
_listen = function (key, fn, cache) {
if(!cache[key]) {
cache[key] = [];
}
cache[key].push(fn);
};
_remove = function (key, cache, fn) {
if(cache[key]) {
if (fn) {
for(let i = cache[key].length; i >= 0; i--) {
if(cache[key][i] === fn) cache[key].splice(i, 1);
}
} else {
cache[key] = []
}
}
};
_trigger = function () {
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key];
if (!stack || !stack.length) return;
return each(stack, function() {
return this.apply(_self, args);
})
};
_create = function (nameSpace) {
let nameSpace = nameSpace || _default;
var cache = {},
offlineStack = [], // 离线事件
ret = {
listen (key, fn, last) {
_listen(key, fn, cache);
if (offlineStack === null) return
if (last === 'last'){
offlineStack.length && offlineStack.pop()();
} else {
each(offlineStack, function () { this()});
}
offlineStack = null;
},
one (key, fn, last) {
_remove(key,cache);
this.listen(key, fn, last)
},
remove (key, fn) {
_remove(key, cache, fn);
},
trigger () {
var fn,
args,
_self = this;
_unshift.call(arguments, cache);
args = arguments;
fn = function () {
return _trigger.apply(_self, args);
};
if(offlineStack) {
return offlineStack.push(fn);
}
return fn();
}
};

return nameSpace?(nameSpaceCache[nameSpace]?nameSpaceCache[nameSpace]:nameSpaceCache[nameSpace] = ret):ret;

};
return {
create: _create,
one(key, fn, last) {
var event = this.create();
event.one(key, fn, last);
},
remove(key, fn) {
var event = this.create();
event.remove(key, fn)
},
listen (key, fn, last) {
var event = this.create();
event.listen(key, fn, last);
},
trigger () {
var event = this.create();
event.trigger.apply(this,arguments);
}
}
}();
return Event;
})()

Event.listen('click', function (a) {
console.log(a)
})
Event.trigger('click', 1);

Event.create('nameSpace1').listen('click', function (a) {
console.log(a);
})
Event.create('nameSpace1').trigger('click', 1)