Javascript实现简单地发布订阅模式
不论是在程序世界里还是现实生活中,发布—订阅模式的应用都非常广泛。我们先看一下现实中的例子。
小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼MM告诉小明,不久后还有一些尾盘推出。开发商正在办理相关手续,手续办好后便可以购买。但到底是什么时候,目前还没有人能够知道。
于是小明记下了售楼处的电话,以后每天都会打电话过去询问是不是已经到了购买时间。除了小明,还有小红,小强,小龙也会每天向售楼处咨询这个问题。一个星期过后,售楼MM决定辞职,因为厌倦了每天回答1000个相同的电话。
当然现实中没有这么笨的销售公司,实际上故事是这样的:小明离开之前,把电话号码留在售楼处。售楼MM答应他,新楼盘一推出就马上发信息通知小明。小红、小强、小龙也是一样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼MM会翻开花名册,遍历上面的电话号码,依次发送一条短信通知他们。
发布订阅模式的优点
-
可以广泛应用于异步编程,它可以代替我们传统的回调函数,我们不需要关注对象在异步执行阶段的内部状态,我们只关心事件完成的时间点。
-
取代对象之间硬编码通知机制,一个对象不必显式调用另一个对象的接口,而是松耦合的联系在一起
虽然不知道彼此的细节,但不影响相互通信。更重要的是,其中一个对象改变不会影响另一个对象。
我们实现一个简单地发布订阅模型:
1 // 首先定义消息的发布者 2 const salesoffice = {}; 3 4 // 定义缓存列表,存放订阅者的回调函数列表; 5 salesoffice.clientList =[]; 6 7 // 设置订阅者 8 9 salesoffice.listen = function(key, fn){ 10 if(!this.clientList[key]) { 11 this.clientList[key] = []; 12 } 13 this.clientList[key].push(fn); 14 } 15 16 // 设置发布事件 17 salesoffice.trigger = function(){ 18 var key = Array.prototype.shift.call(arguments), 19 fns = this.clientList[key]; 20 if(!fns || fns.length === 0) { 21 return false; 22 } 23 for(var i = 0 , fn ; fn = fns[i++];) { 24 fn.apply(this, arguments); 25 } 26 } 27 28 //示例 29 30 salesoffice.listen('aaaaa' , function(a, b){ 31 console.log(a); 32 console.log( b); 33 }) 34 35 36 salesoffice.listen('abcde' , function(a, b){ 37 console.log(a); 38 console.log(b); 39 console.log(a+b); 40 }) 41 42 console.log(salesoffice); 43 44 salesoffice.trigger('aaaaa' , 100 , 200); // 100 200 300 45 46 salesoffice.trigger('abcde', 111 , 222); // 111 222 333
一个通用的发布订阅模型和事件
1 // 定义发布/订阅模型 2 const event = { 3 // 设置缓存列表,存放订阅者的回调函数列表 4 list : [], 5 // 设置订阅者 6 listen : function(key , fn){ 7 if(!this.list[key]){ 8 this.list[key] = []; 9 } 10 // 将订阅的消息添加到缓存列表中 11 this.list[key].push(fn); 12 }, 13 14 // 发布事件 15 trigger : function() { 16 const key = Array.prototype.shift.call(arguments), 17 fns = this.list[key]; 18 if(!fns || fns.length === 0){ 19 return false; 20 } 21 for(let i = 0 , fn ; fn = fns[i++];){ 22 fn.apply(this, arguments); 23 } 24 }, 25 26 // 取消订阅 27 remove : function(key , fn){ 28 const fns = this.list[key]; 29 // 如果key对应的消息没有订阅过的话,则返回 30 if(!fns) { 31 return false; 32 } 33 // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅 34 if(!fn) { 35 delete this.list[key]; //如果没有后续参数,则删除整个回调数组 36 }else { 37 for(let i = fns.length - 1 ; i>=0 ;i--) { 38 const _fn = fns[i]; 39 if(_fn === fn) { 40 fns.splice( i, 1); // 删除订阅者的回调函数 41 } 42 } 43 } 44 } 45 }; 46 47 const initEvent = function(obj) { 48 for(let i in event) { 49 obj[i] = event[i]; 50 } 51 }; 52 53 const shoeobj = {}; 54 55 initEvent(shoeobj); 56 57 shoeobj.listen('abcd' , function(a, b) { 58 console.log(a); 59 console.log(b); 60 }) 61 62 shoeobj.listen('efgh' , function(a, b){ 63 console.log(a); 64 console.log(b); 65 }) 66 67 shoeobj.trigger('abcd' , 100 ,200); // 100 200 68 69 shoeobj.trigger('efgh' , 300, 500); // 300 500 70 71 shoeobj.remove('abcd'); 72 73 shoeobj.trigger('abcd', 20, 50); // false