JavaScript设计模式之观察者模式(发布者-订阅者模式)
观察者模式(又叫发布者-订阅者模式)应该是最常用的模式之一.在很多语言里都得到大量应用.包括我们平时接触的dom事件.也是js和dom之间实现的一种观察者模式.
div.onclick = functionclick(){ alert(”click') }
只要订阅了div的click事件.当点击div的时候,functionclick就会被触发。
那么到底什么是观察者模式呢.先看看生活中的观察者模式。
好莱坞有句名言.“不要给我打电话,我会给你打电话”.这句话就解释了一个观察者模式的来龙去脉。其中“我”是发布者,“你”是订阅者。
再举个例子,我来公司面试的时候,完事之后每个面试官都会对我说:“请留下你的联系方式,有消息我们会通知你”。在这里“我”是订阅者,面试官是发布者。所以我不用每天或者每小时都去询问面试结果,通讯的主动权掌握在了面试官手上。而我只需要提供一个联系方式。
观察者模式可以很好的实现2个模块之间的解耦。假如我正在一个团队里开发一个html5游戏.当游戏开始的时候,需要加载一些图片素材。加载好这些图片之后开始才执行游戏逻辑.假设这是一个需要多人合作的项目.我完成了Gamer和Map模块,而我的同事A写了一个图片加载器loadImage。
loadImage的代码如下:
loadImage( imgAry, function(){ Map.init(); Gamer.init(); })
当图片加载好之后,再渲染地图,执行游戏逻辑.嗯,这个程序运行良好.突然有一天,我想起应该给游戏加上声音功能.我应该让图片加载器添上一行代码.
loadImage( imgAry, function(){ Map.init(); Gamer.init(); Sount.init(); })
可是写这个模块的同事A去了外地旅游.于是我打电话给他,喂.你的loadImage函数在哪,我能不能改一下,改了之后有没有副作用.如你所想,各种不淡定的事发生了.如果当初我们能这样写呢:
loadImage.listen(”ready',function(){ Map.init(); }) loadImage.listen(”ready',function(){ Gamer.init(); }) loadImage.listen(”ready',function(){ Sount.init(); })
loadImage完成之后,它根本不关心将来会发生什么,因为它的工作已经完成了.接下来它只要发布一个信号.
loadImage.trigger(”ready');
那么监听了loadImage的'ready'事件的对象都会收到通知.就像上个面试的例子.面试官根本不关心面试者们收到面试结果后会去哪吃饭.他只负责把面试者的简历搜集到一起.当面试结果出来时照着简历上的电话挨个通知.
说了这么多概念,来一个具体的实现.实现过程其实很简单.面试者把简历扔到一个盒子里,然后面试官在合适的时机拿着盒子里的简历挨个打电话通知结果.
Events=function(){ varlisten,log,obj,one,remove,trigger,__this; obj={}; __this=this; listen=function(key,eventfn){ //把简历扔盒子,key就是联系方式. varstack,_ref; //stack是盒子 stack=(_ref=obj[key])!=null?_ref:obj[key]=[]; returnstack.push(eventfn); }; one=function(key,eventfn){ remove(key); returnlisten(key,eventfn); }; remove=function(key){ var_ref; return(_ref=obj[key])!=null?_ref.length=0:void0; }; trigger=function(){ //面试官打电话通知面试者 varfn,stack,_i,_len,_ref,key; key=Array.prototype.shift.call(arguments); stack=(_ref=obj[key])!=null?_ref:obj[key]=[]; for(_i=0,_len=stack.length;_i<_len;_i++){ fn=stack[_i]; if(fn.apply(__this, arguments)===false){ returnfalse; } } return{ listen:listen, one:one, remove:remove, trigger:trigger } }
最后用观察者模式来做一个成人电视台的小应用.
//订阅者 varadultTv=Event(); adultTv.listen( ”play', function(data){ alert(“今天是谁的电影”+data.name); }); //发布者 adultTv.trigger( ”play', {‘name':‘麻生希'} )