查看原文
其他

js中的一对多 - 订阅发布模式

2016-10-22 jimmy_thr 前端圈

订阅发布模式如果按数学翻译其实就是.一对多的映射关系.怎么解释呢? 就是一个开关,同时并联几个灯泡(在不同房间),触发的时候,几个灯泡都会得到指令,然后执行发光的行为。


订阅发布模式

这种模式在js里面有这天然的优势,因为js本身就是事件驱动型语言。比如,页面上有一个button, 你点击一下就会触发上面的click事件,而此时有一部分程序正在监听这个事件,随之触发相关的处理程序。


  1. var button = $("#button"); 

  2. button.on("click",function(){ 

  3.     console.log("I am pressing the button"); 

  4. });


事实上,我们也早就熟悉这个模式了,只是不知道这叫什么(订阅发布模式 又名 观察者模式).这个模式最大的一个好处就在于,能够解耦回调函数,让你的程序看起来更美观(虽然现在有Promise和Deferred帮忙,但是不彻底)。


订阅发布模式内涵


说了点理论,来些干货。(以下将订阅发布模式简称为观察者模式)。
观察者模式无非就两个部分,一个订阅(监听程序),一个发布(触发事件)。而他们中间的链接枢纽就是事件。 通常来说,我们可以自定义一个观察者模式--使用自定义事件。


(由于IE过于SB,我就不想说他的事了,下面这些适用于chrome, iE9+,ff等其他现代浏览器中)


  1. // Create the event.   

  2. //创建一个事件 

  3. var event = document.createEvent('Event'); 

  4. //Event是自定义的事件名 

  5.  

  6. // Define that the event name is 'build'. 

  7. event.initEvent('build'truetrue); 

  8. //这里是初始化事件,就是些参数而已. 

  9.  

  10. // Listen for the event. 

  11. elem.addEventListener('build'function (e) { 

  12. //给事件添加监听 

  13.   // e.target matches elem 

  14. }, false); 

  15.  

  16. // target can be any Element or other EventTarget. 

  17. elem.dispatchEvent(event); 

  18. //触发事件


大致就是这几个步骤,由于这样写,太非人道了。所以这里映入jquery的trigger触发方式(大哥就是大哥~)在jquery的事件处理中,几个基本和事件相关的API需要熟悉。一个是on,一个是trigger。


  1. $(".ele").on("click",function(){ 

  2.   console.log("clicking"); 

  3. }); 

  4. $(".ele").trigger("click");


这是一个基本的使用,使用trigger来触发事件。


但是谁无聊到连click自己手动触发啊,这个例子只是讲解。现在说一下精华-自定义事件。


在jquery里面,可以直接使用on来进行自定义事件的模拟。


  1. ele.on("stimulate",function(){ 

  2.     //订阅一个事件 

  3.     ...do sth 

  4. }); 

  5. ele.trigger("stimulate"); 

  6. //发布一个事件


这里trigger只是起到一个开关的作用,那么我想要他变为一个管道可以吗?
absolutely!!!
在trigger里面还有第二个参数可以选择,即[data]


  1. $(document.body).on("stimulate",function(event,name1,name2){ 

  2.     //和节点有关的事件里,第一个参数永远是event 

  3.     console.log(name1,name2); 

  4.     //"jimmy","sam" 

  5. }); 

  6. $(document.body).trigger("stimulate",["jimmy","sam"]);


而且如果你自定义事件过多,起名也是件死人的事。所以牛逼的jq会帮你把命名空间处理好。


  1. ele.on("stimulate.jimmy.sam",function(){ 

  2.     //使用"."链接 

  3.     ...do sth 

  4. });


他的作用域就是sam>jimmy>stimulate这样一个关系。
详情可以参考: Aron大神些的jquery事件解析。这里我直接把trigger的源码贴出来吧.以供参考:

https://jsfiddle.net/jimmyTHR/7e49zpL7/

其实上面的自定义事件的用法也非常有限,因为如果使用一个节点作为载体的话,这样的成本也太大了。所以一般在业内已经有成熟的自定义事件的插件了.
不过为了深入理解观察者模式,我们一步一步来.(为了装逼)


订阅发布模式进阶


经过上面的唐僧咒,大家也应该差不多熟悉这个模式的一些关键部分。订阅,发布,事件。好,我们就这3个部分来自己模拟一个。


  1. //摘自alloyTeam团队的曾探·著 

  2. var imitate = (function() { 

  3.     var imitate = { 

  4.         clientList: [], 

  5.         listen: function(key, fn) { 

  6.             if (!this.clientList[key]) { 

  7.                 this.clientList[key] = []; 

  8.             } 

  9.             this.clientList[key].push(fn); 

  10.         }, 

  11.         trigger: function() { 

  12.             var key = [].shift.call(arguments); 

  13.             var fns = this.clientList[key]; 

  14.  

  15.             // 如果没有对应的绑定消息 

  16.             if (!fns || fns.length === 0) { 

  17.                 return false

  18.             } 

  19.  

  20.             for (var i = 0, fn; fn = fns[i++];) { 

  21.                 // arguments 是 trigger带上的参数 

  22.                 fn.apply(this, arguments); 

  23.             } 

  24.         } 

  25.     } 

  26.     return function() { 

  27.        return Object.create(imitate); 

  28.     } 

  29. })(); 

  30. var eventModel = imitate(); 

  31. //得到上面的对象 

  32. eventModel.listen("jimmy",function(){console.log("jimmy");}); 

  33. //jimmy 

  34. eventModel.trigger("jimmy");


恩,这样下来皆可以和重重的节点说拜拜了。直接使用imitate就可以进行事件的模拟,而且超快.当然,这样写改进的空间还是挺大的。解决命名空间问题(暂不管),删除订阅问题(这个用处不大)...目前我们先着手解决这个问题。


  1. var Event = (function() { 

  2.     var clientList = {}; 

  3.     var listen, 

  4.         trigger, 

  5.         remove; 

  6.     listen = function(key, fn) { 

  7.         if (!clientList[key]) { 

  8.             clientList[key] = []; 

  9.         } 

  10.         clientList[key].push(fn); 

  11.     }; 

  12.  

  13.     trigger = function() { 

  14.         var key = [].shift.call(arguments); 

  15.         var fns = clientList[key]; 

  16.  

  17.         if (!fns || fns.length === 0) { 

  18.             return false

  19.         } 

  20.  

  21.         for (var i = 0, fn; fn = fns[i++];) { 

  22.             fn.apply(this, arguments); 

  23.         } 

  24.     }; 

  25.  

  26.  

  27.     remove = function(key, fn) { 

  28.         var fns = clientList[key]; 

  29.  

  30.         // key对应的消息么有被人订阅 

  31.         if (!fns) { 

  32.             return false

  33.         } 

  34.  

  35.         // 没有传入fn(具体的回调函数), 表示取消key对应的所有订阅 

  36.         if (!fn) { 

  37.             fns && (fns.length = 0); 

  38.         } 

  39.         else { 

  40.             // 反向遍历 

  41.             for (var i = fns.length - 1; i >= 0; i--) { 

  42.                 var _fn = fns[i]; 

  43.                 if (_fn === fn) { 

  44.                     // 删除订阅回调函数 

  45.                     fns.splice(i, 1); 

  46.                 } 

  47.             } 

  48.         } 

  49.     }; 

  50.  

  51.     return { 

  52.         listen: listen, 

  53.         trigger: trigger, 

  54.         remove: remove 

  55.     } 

  56. }());


这个Event对象,能够解决大部分事件模拟的问题。说了这么多,md,实例嘞。。。等等。马上来


发布订阅模式的实战


如果大家写过登录框(异步登录哈),应该知道.登录框和header的部分是完全不同的两个部分。这个场景就很适合发布订阅模式了。看一下。如果没有发布订阅模式的代码:


  1. login.on("click",function(){ 

  2.     var name = $(".username").val().trim; 

  3.     http.login(name) 

  4.     //使用异步Deferred书写 

  5.     .then(function(data){ 

  6.         //以下填写乱七八糟的处理 

  7.         changeName(); 

  8.         changeAvtar(); 

  9.         changeStatus(); 

  10.         ... 

  11.     }) 

  12. });


使用发布订阅模式

  1. login.on("click",function(){ 

  2.     var name = $(".username").val().trim; 

  3.     http.login(name)  //使用异步Deferred书写 

  4.     .then(function(data){ 

  5.         Event.trigger("login",data);  //发布我登录成功的状态,并传入参数 

  6.     }) 

  7. }); 

  8. var header = (function() { 

  9.     Event.listen("login"function(data) { 

  10.         header.changeAvator(data); 

  11.     }) 

  12.     return { 

  13.         changeAvator: function(data) { 

  14.             ...换头像 

  15.         } 

  16.     } 

  17. })(); 

  18. var bar = (function() { 

  19.     Event.listen("login"function(data) { 

  20.         bar.changeName(data); 

  21.     }) 

  22.     return { 

  23.         changeName: function(data) { 

  24.             ...换名字 

  25.         } 

  26.     } 

  27. })();


可以清楚的看到,如果你的登录状态改变了的话,会有一系列的订阅程序发生.而且每个订阅之间互不干扰,你可以随便添加或者删除订阅,这都不会影响你的登录的执行逻辑. 当然发布订阅的使用肯定不会仅仅局限于,登录状态的改变。还可以应用于,模块间信息的传递,分页页面的渲染等。但是使用的时候,一定要慎重,因为你订阅的越多,bug的查找也会越复杂。所以,发布订阅模式使用的时候,希望大家好好想一想,不要为了模式而去模式。


【您可能感兴趣的文章】

一、移动端h5开发相关内容总结(四)

二、移动端h5开发相关内容总结(三)

三、如何打造黄金团队?Google招募人才的8个重点

四、[CSS篇]移动端 h5开发相关内容总结

五、[JavaScript 篇]移动端h5开发相关内容总结

六、[译]使用 Yarn 安装 Vue-cli

七、[译]取代 npm 的新利器 Yarn

八、git分支进阶

九、入职新手必知的劳动法

十、再谈移动端适配和点5像素的由来


前端圈--打造专业的前端技术会议

为web前端开发者提供技术分享和交流的平台

打造一个良好的前端圈生态,推动web标准化的发展

官网:http://fequan.com

微博:fequancom | QQ群:41378087


长按二维码关注我们

投稿:content@fequan.com

赞助合作:apply@fequan.com

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存