发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
一、定义
发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
二、作用
- 可广泛应用在异步编程中,作为回调函数的替代方案,相比回调更易维护,更简洁;
- 发布订阅模式更利于不同对象之间的空间解耦和时间解耦,一个对象不用再显示地调用对一个对象的接口;
三、标准浏览器的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');