当前位置: 首页 > 后端技术 > Node.js

从观察者模式到手写EventEmitter源码

时间:2023-04-03 20:45:43 Node.js

观察者模式观察者模式(observer)广泛应用于javascript语言,浏览器事件(如鼠标点击,键盘事件keyDown)就是这种模式的例子。设计这种模式的主要原因是为了促进低耦合的形成。在这种模式中,一个对象“订阅”另一个对象的活动,而不是简单地调用一个对象。当对象的活动状态发生变化时,就去通知订阅者,订阅者也叫观察者。生活中订阅报纸就像从报社订购报纸一样。想看什么报纸都可以去报社付费订阅。当有新报纸发行时,报社会发一份给所有订阅该报纸的人。可以接收订阅者。我们可以使用这个例子来模拟它使用javascript。假设有一个出版商杰克,他每天出版报纸和杂志,订阅者汤姆会随时收到新闻发生的通知。Jack必须有一个subscribers属性,它是一个数组类型,订阅行为会按顺序存储在这个数组中,通知就是调用订阅者对象的某个方法。因此,当用户Tom订阅信息时,订阅者为Jack的subscribe()提供了他的方法之一。当然,您也可以退订。我不想再看报纸了,所以我调用unsubscribe()取消订阅。一个简单的观察者模式应该有以下成员:订阅一个数组subscribe()向数组添加一个订阅unsubscribe()从数组中删除一个订阅publish()遍历数组,在订阅时调用方法这种模式还需要一个类型参数用于区分订阅类型。比如有的人订阅娱乐新闻,有的人订阅体育杂志。使用此属性进行标记。我们使用简单的代码来实现:varJack={subscribers:{'any':[]},//添加订阅subscribe:function(type='any',fn){if(!this.subscribers[type]){this.subscribers[type]=[];}this.subscribers[type].push(fn);//在数组中保存订阅方法},//取消订阅unsubscribe:function(type='any',fn){this.subscribers[type]=this.subscribers[type].filter(function(item){returnitem!==fn;});//从数组中移除取消订阅方法},//发布和订阅publish:function(type='any',...args){this.subscribers[type].forEach(function(item){item(...args);//根据不同的类型调用相应的方法});}};以上是观察者模式最简单的实现。可以看到代码非常简单。核心原理就是把订阅的方法按照分类存放在一个数组中,发布的时候取出来执行。下面使用Tom订阅报纸:varTom={readNews:function(info){console.log(info);}};//Tom订阅了Jack的报纸Jack.subscribe('Entertainment',Tom.readNews);Jack.subscribe('Sports',Tom.readNews);//阿汤哥退订娱乐新闻:Jack.unsubscribe('Entertainment',Tom.readNews);//发布新报纸:Jack.publish('娱乐','S.H.E演唱会惊喜debut')Jack.publish('Sports','欧洲联赛-意大利0-1客场负于葡萄牙');运行结果:实际应用欧联杯-意大利0-1客场负于葡萄牙的比赛可以看出观察者的格局,使得两个对象之间的关系非常松散。当不需要订阅关系时,删除订阅语句。那么这种模式在实际应用中用在了哪些地方呢?events模块node.jsevents是一个使用率很高的模块。其他的node.js原生模块都是基于它的,比如streaming、HTTP等。我们可以手写一个版本的事件核心代码,看看观察者模型的实际应用。events模块的作用是事件绑定,所有继承自它的实例都具有处理事件的能力。首先是一个类,我们写下它的基本结构:functionEventEmitter(){//私有属性,保存订阅方法this._events={};}//默认最大监听数EventEmitter.defaultMaxListeners=10;module.exports=EventEmitter;接下来我们将事件的核心方法一一实现。on方法首先是on方法,用于订阅事件。在老版本的node.js中是addListener方法。它们是同一个函数:EventEmitter.prototype.on=EventEmitter.prototype.addListener=function(type,listener,flag){//保证实例属性存在if(!this._events)this._events=Object.create(无效的);if(this._events[type]){if(flag){//插入这个。_events[类型].unshift(侦听器);}else{this._events[type].push(listener);}}else{this._events[type]=[listener];}//绑定事件,触发newListenerif(type!=='newListener'){this.emit('newListener',type);}};因为还有其他子类需要继承EventEmitter,所以需要判断子类是否有_event属性。这样做是为了确保子类必须具有此实例属性。flag标签是订阅方法的插入标识。如果为“真”,则认为它被插入到数组的头部。可以看到,这是观察者模式的订阅方法的实现。发出方法EventEmitter.prototype.emit=function(type,...args){if(this._events[type]){this._events[type].forEach(fn=>fn.call(this,...args));}};emit方法是取出并执行subscription方法,用call方法纠正this的方向,使其指向子类的实例。once方法EventEmitter.prototype.once=function(type,listener){let_this=this;//中间函数,调用函数后立即删除订阅only(){listener();_this.removeListener(类型,仅);}//origin保存了原始回调的引用,用于remove的判断only.origin=listener;this.on(type,only);};once方法很有意思,它的作用是订阅事件“一次”,当这个事件被触发后,就不会再触发了。原理就是把订阅方法用一个函数包裹起来,执行完这个函数就去掉。offmethodEventEmitter.prototype.off=EventEmitter.prototype.removeListener=function(type,listener){if(this._events[type]){//过滤掉取消订阅方法,从数组中移除this._events[type]=this._events[type].filter(fn=>{returnfn!==listener&&fn.origin!==listener});}};off方法就是取消订阅,原理和观察者模式是一样的,subscription方法只是简单的从数组中移除。prependListener方法EventEmitter.prototype.prependListener=function(type,listener){this.on(type,listener,true);};不用说,这个方法调用on方法把mark传给true(在header中插入订阅方法)即可。以上,实现了EventEmitter类的核心方法。总结松散耦合是通过创建“可观察的”对象来实现的,这些对象在感兴趣的事件发生时通知所有观察者。部分例子参考《JavaScript模式》作者:StoyanStefanov