发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
一、定义
发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
二、作用
- 可广泛应用在异步编程中,作为回调函数的替代方案,相比回调更易维护,更简洁;
- 发布订阅模式更利于不同对象之间的空间解耦和时间解耦,一个对象不用再显示地调用对一个对象的接口;
三、标准浏览器的DOM自定义事件
标准浏览器可以使用document.createEvent()
函数自定义事件(支持的事件),使用的大致流程如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//创建事件
var evt = document.createEvent('Event');
//初始化事件
evt.initEvent('build', //自定义事件名
false, //是否冒泡
true //是否可取消
);
var elem=document.getElementById("btn");
elem.addEventListener('build', function(e){ //订阅自动以事件
console.log("click.");
}, false);
//这里的发布和订阅是同一个对象,实际上两者不必一样
elem.addEventListener('click', function(e){
elem.dispatchEvent(evt); //发布事件
}, false);
四、通用的全局发布/订阅实现
本样例摘自《JavaScript设计模式与开发实践》一书,次数出自腾讯AlloyTeam团队,作者曾探的写作风格简明易懂,最值得称道的是,本书对每一个模式都关联了非常实用的开发实践样例,值得一读。
通用的发布/订阅实现的核心是:建立一个缓存列表,表中存放的是订阅者的回调函数,事件发布后去遍历这个列表。代码如下(codepen地址:发布/订阅者模式 by zhaojun):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/*发布-订阅模式的全局通用实现
Event是定义的全局对象,
不同对象之间发布-订阅消息通过Event这个中间者,
这样可以很好的实现空间和时间的解耦。
*/
var Event=(function(){
var clientList={}; //事件列表,其中键代表事件名,值为数组类型,存放回调
var listen; //监听事件方法
var trigger; //触发事件方法
var remove; //移除事件方法
listen=function(key,fn){
if(!clientList[key]){
clientList[key]=[]; //创建事件数组
}
clientList[key].push(fn); //回调压入
};
trigger=function(){ //传入参数的格式:事件名,[message]
var key=Array.prototype.shift.call(arguments);
var fns=clientList[key];
if(!fns||fns.length<=0){
return false;
}
for(var i=0,fn;i<fns.length;i++){
fn=fns[i];
fn.apply(this,arguments); //执行回调,arguments已剔除传入的事件名
}
};
remove=function(key,fn){
var fns=clientList[key];
if(!fns){
return false;
}
if(!fn){
fns.length=0; //没有传入具体的函数则清除所有事件
}else{
for(var j=0;j<fns.length;j++){
var _fn=fns[j];
/*两个函数的比较:
原文是用的_fn===fn,
当listen的是两个匿名函数时,这样比较总是得到false,因为这是两个不同对象的比较,
当传入的是另外声明的函数时,可以得到预期的结果,因为它们引用同一个函数对象,
使用toString比较会严格比较两者的区别,虽然两个函数运行起来没有区别,但也可能判为不等,
最终采用soString的原因是:回调函数使用频繁,且可以兼容另外声明的函数,
缺点是:不支持形参不同而函数名相同的函数的判别。
*/
if(_fn.toString()==fn.toString()){
fns.splice(j,1);
}
}
}
};
return{ //返回三个方法
listen:listen,
trigger:trigger,
remove:remove
};
})();
//监听事件,并传入回调
Event.listen('userEventA',function(message){
console.log(message);
});
//触发事件,并调用回调
Event.trigger('userEventA','this a message');