背景:
在现在WEB开发中,稍复杂一点的页面,都会涉及到多个模块,尤其是类似seajs、LABjs、requireJS等模块工具出来后,前端开发者分模块开发已经慢慢变成一种习惯了,但是多个模块间的常常有各种交集,需要通信、需要互相调用,这时监听者模式便是必不可少的一种设计模式了,在前端表现事件操作。backbone和jquery都有提供了很好的事件处理方式。
但是,真正开发需求的时候我们常常会遇到文件加载顺序跟事件监听与更新不一致的需求,比如说:在一个视频网站里面,有一个视频处理的JS模块和用户信息处理模块,现在需要先加载视频模块的JS使得能第一时间把视频展示给用户看,但是在展示完视频的同时需要视频JS模块会更新一个在用户信息处理模块里面监听的发送用户查看视频记录的模块,这时候由于用户信息处理模块未加载,所以会导致事件未定义且不被执行,这时我们希望的是就算是之前更新的,用户信息处理模块在初始化监听时能知道之前已经被更新过了,并执行该更新。下面就是来实现不依赖JS加载顺序事件对象的实现吧:
基础:
(function(){ var obListenList = {}; //监听者事件列表 var OB = { listen : function(listenStr,func){ if(!obListenList[listenStr]){ obListenList[listenStr] = []; } obListenList[listenStr].push(func); }, update : function(listenStr,datas){ if(typeof obListenList[listenStr] != "undefined"){ var len = obListenList[listenStr].length, i = 0; for( ; i < len ; i++){ if(typeof(obListenList[listenStr][i])=="function"){ obListenList[listenStr][i](datas); } } } } }; window.OB = OB; }()); OB.listen("say",function(msg){ document.write(msg + "<br/>"); }); OB.update("say","She say : Im your's!");
基础的事件编写很简单,只需要用一个对象把被监听的事件的函数列表放在一个对象(obListenList)里面,在update的时候查看遍历调用就是了。
实现先更新,等监听的时候也会执行之前的更新:
(function(){ var obListenList = {}, //监听事件列表 obUpdateList = {} , // 更新事件列表 obUpdateListLen = 2 , //最大监听更新队列数 obUpdateWaitFlag = true ; var OB = { listen : function(listenStr,func){ if(!obListenList[listenStr]){ obListenList[listenStr] = []; } obListenList[listenStr].push(func); //查看之前是否有更新过 if(typeof obUpdateList[listenStr] != "undefined"){ var lStrArg = obUpdateList[listenStr]; for(var i = 0 ; i < lStrArg.length; i ++ ){ func(lStrArg[i]); } } }, update : function(listenStr,datas){ if(typeof obListenList[listenStr] != "undefined"){ var len = obListenList[listenStr].length, i = 0; for( ; i < len ; i++){ if(typeof(obListenList[listenStr][i])=="function"){ obListenList[listenStr][i](datas); } } } if(obUpdateWaitFlag){ //往该事件更新队列里面添加数据 if(typeof obUpdateList[listenStr] == "undefined"){ obUpdateList[listenStr] = []; } obUpdateList[listenStr].push(datas); //如果超过允许的最大长度就不要再往里面添加了,防止内在溢出 if(obUpdateList[listenStr].length>=obUpdateListLen){ obUpdateWaitFlag = false; } } } }; window.OB = OB; }()); OB.update("say","She say : Im your's!--1"); OB.update("say","She say : Im your's!--2"); OB.update("say","She say : Im your's!--3"); OB.listen("say",function(msg){ document.write(msg + "<br/>"); }); OB.update("say","She say : Im your's!--4"); /* 结果是: * She say : Im your's!--1 * She say : Im your's!--2 * She say : Im your's!--4 */
看下代码,实现起来也不是很难有三个关键点:
1,在更新事件的时候将事件传入的参数传进更新事件列表里面
2,在监听事件初始化后,查看更新事件列表里面有没有对应事件需要更新的数据,如果有,便遍历执行一遍
3,一定要给更新事件列表设定一个最大保留监听的事件长度(obUpdateListLen),因为在一些复杂的页面程序中可能会有某个无限的更新,如果不做限制必然导致内存溢出。
优化:
(function() { if(typeof OB !== "undefined"){//防止文件重复加载导致事件队列被清空 return ; } var obListenList = {} , //存储监听队列 obUpdateList = {} , //存储更新队列 obUpdateListLen = 2 , //存储更新队列最多保存长度 obUpdateWaitFlag = true ; //记录是否达到了存储更新队列的最大值 var OB = function (listenStr){ return new OB.prototype.init(listenStr); }; OB.prototype = { listenStr : "", instance : null, init : function(listenStr){ if(OB.prototype.instance){ OB.prototype.instance.setListenStr(listenStr); return OB.prototype.instance; } this.setListenStr(listenStr); OB.prototype.instance = this; return this; }, setListenStr : function(listenStr){ this.listenStr = listenStr; }, listen : function(func){ if(this.listenStr){ if(!obListenList[this.listenStr]){ obListenList[this.listenStr] = []; } obListenList[this.listenStr].push(func); //查看之前是否有更新过 if(typeof obUpdateList[this.listenStr] != "undefined"){ var lStrArg = obUpdateList[this.listenStr]; for(var i = 0 ; i < lStrArg.length; i ++ ){ func.apply(this,lStrArg[i]); } } } }, update : function(){ if(typeof obListenList[this.listenStr] != "undefined"){ var len = obListenList[this.listenStr].length, i = 0; for( ; i < len ; i++){ if(typeof(obListenList[this.listenStr][i])=="function"){ obListenList[this.listenStr][i].apply(this,arguments); } } } if(obUpdateWaitFlag){ if(typeof obUpdateList[this.listenStr] == "undefined"){ obUpdateList[this.listenStr] = []; } obUpdateList[this.listenStr].push(arguments); if(obUpdateList[this.listenStr].length>=obUpdateListLen){ obUpdateWaitFlag = false; } } } }; OB.prototype.init.prototype = OB.prototype; window.OB = OB; //兼容seaJS和requireJS if ( typeof define === "function" && ( define.cmd || define.amd ) ) { define( "observer", [], function() { return OB; }); } }()); OB("say").update("She say : Im your's!--1"); OB("say").update("She say : Im your's!--2"); OB("say").update("She say : Im your's!--3"); OB("say").listen(function(msg){ document.write(msg + "<br/>"); }); OB("say").update("He Say : yes! you are mine!"); /* 结果是: * She say : Im your's!--1 * She say : Im your's!--2 * He Say : yes! you are mine! */
这里主要是几个点的优化:
1,操作优化,使用了JQuery原型模式实现了JQuery操作方式,具体的原理可以参考《犀利开发:jQuery内核详解与实践》 这本书
2,支持AMD和CMD模块,方便直接应用到SeaJS或requireJS等项目
3,防止重复加载导致事件列表被清空
这个只是本人的实践经历,写法可能没有那么优美,但是基本功能已经出来了,希望对大家的项目有所帮助,如果有存在什么问题,也可以回复一下。