JavaScript事件最核心的包括事件监听(addListener)、事件触发(emit)、事件删除(removeListener),理解如下:
考虑一个DOM事件:
//Typescriptconstbutton=document.querySelector('button');button.addEventListener("click",(event)=>{//dosomethingwiththeevent})我们向按钮单击事件添加了一个listener(监听器),并且已经订阅了一个正在被发出的事件,当事件发生时会触发回调。每次单击该按钮时,都会发出该事件,而该事件会触发回调。
当处理现有代码库时,或许需要触发自定义事件。不像单击按钮这样的特定DOM事件,而是假设想基于其他触发器发出一个事件,并得到一个事件响应。我们需要一个自定义事件派发器来实现这一点。
事件派发器是一种模式,它监听一个已命名的事件,触发回调,然后发出该事件并附带一个值。有时这被称为“发布/订阅”模型或监听器。它们指的是同一件事。
发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
在JavaScript开发中,我们一般用事件模型来替代传统的发布—订阅模式。
发布者有一个订阅者缓存队列
发布者有增加和删除订阅者的方法
发布者状态改变,需要notify方法通知队列中的所有订阅者
js中采用事件回调的方式来更新订阅者,因此订阅者不再需要update方法
下面来模拟下EventEmitter的初步实现
once(eventName,listener){functionwrap(args){listener.apply(this,args);this.removeListener(eventName,wrap);}wrap.cb=listener;//将回调存储起来用于删除时对比this.on(eventName,wrap);}复制代码将回调函数包裹起来,在包裹函数内部移除原回调函数,然后将wrap函数添加进观察者队列。同时要将原回调函数存进wrap中,用在在移除原回调时判断。
this.emmit('newListener',eventName,listener);//触发newListener事件回调复制代码defaultMaxListeners这个静态属性限制了一种事件可以添加的最大回调数量,同时还有配套的setMaxListeners和getMaxListeners方法来设置和获取每个事件可以添加的最大回调数量
setMaxListeners(n){this.maxListeners=n;}getMaxListeners(){returnthis.maxListenersthis.maxListeners:EventEmitter.defaultMaxListeners;}复制代码on方法添加判断;
这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或者覆盖。
用法举例:
letmySymbol=Symbol();//第一种写法leta={};a[mySymbol]='Hello!';//第二种写法leta={[mySymbol]:'Hello!'};//第三种写法leta={};Object.defineProperty(a,mySymbol,{value:'Hello!'});//以上写法都得到同样结果a[mySymbol]//"Hello!"Object.getOwnPropertySymbolsObject.getOwnPropertySymbols()方法返回一个数组,包含给定对象所有自有的Symbol值的属性(包括不可枚举的Symbol值属性)。语法
Object.getOwnPropertySymbols(obj);//参数obj:要获取自有Symbol值属性的对象;返回值一个包含给定对象所有自有的Symbol值的属性的数组。所有的对象在初始化时都不会包含任何的Symbol值属性,除非在对象上显式定义了Symbol值属性,否则该方法会返回一个空数组。
例:获取对象自有的Symbol值属性
vara=Symbol('a');varb=Symbol('b');varobj={};obj[a]=1;obj[b]=2;Object.getOwnPropertySymbols(obj);//[Symbol(a),Symbol(b)]varc=Symbol('c');Object.defineProperty(obj,c,{value:3,enumerate:false,writable:false,configuration:false});Object.getOwnPropertySymbols(obj);//[Symbol(a),Symbol(b),Symbol(c)]三、源码分析EventEmitter3是一个典型的第三方事件库,能够让我们自定义实现多个函数与组件间的通信。
本项目的结构比较清晰,主要包括的内容是:
表示单个事件侦听器的EE
Prototype属性:保存事件与监听器的_events属性
方法的定义
EventEmitter.prototype上的方法定义
为removeListener和on方法别名
prefix和EventEmitter导出
下面对内容进行具体讲解
/***Representationofasingleeventlistener.**@param{Function}fnThelistenerfunction.*@param{*}contextThecontexttoinvokethelistenerwith.*@param{Boolean}[once=false]Specifyifthelistenerisaone-timelistener.*@constructor*@private*/functionEE(fn,context,once){this.fn=fn;this.context=context;this.once=once||false;}EE:
/***Addalistenerforagivenevent.**@param{EventEmitter}emitterReferencetothe`EventEmitter`instance.*@param{(String|Symbol)}eventTheeventname.*@param{Function}fnThelistenerfunction.*@param{*}contextThecontexttoinvokethelistenerwith.*@param{Boolean}onceSpecifyifthelistenerisaone-timelistener.*@returns{EventEmitter}*@private*/functionaddListener(emitter,event,fn,context,once){if(typeoffn!=='function'){thrownewTypeError('Thelistenermustbeafunction');}varlistener=newEE(fn,context||emitter,once),evt=prefixprefix+event:event;if(!emitter._events[evt])emitter._events[evt]=listener,emitter._eventsCount++;elseif(!emitter._events[evt].fn)emitter._events[evt].push(listener);elseemitter._events[evt]=[emitter._events[evt],listener];returnemitter;}addListener:
/***Cleareventbyname.**@param{EventEmitter}emitterReferencetothe`EventEmitter`instance.*@param{(String|Symbol)}evtTheEventname.*@private*/functionclearEvent(emitter,evt){if(--emitter._eventsCount===0)emitter._events=newEvents();elsedeleteemitter._events[evt];}clearEvent:
/***Minimal`EventEmitter`interfacethatismoldedagainsttheNode.js*`EventEmitter`interface.**@constructor*@public*/functionEventEmitter(){this._events=newEvents();this._eventsCount=0;}EventEmitter:
/***Returnanarraylistingtheeventsforwhichtheemitterhasregistered*listeners.**@returns{Array}*@public*/EventEmitter.prototype.eventNames=functioneventNames(){varnames=[],events,name;if(this._eventsCount===0)returnnames;for(namein(events=this._events)){if(has.call(events,name))names.push(prefixname.slice(1):name);}//Object.getOwnPropertySymbols()方法返回一个数组,包含给定对象所有自有的Symbol值的属性(包括不可枚举的Symbol值属性)if(Object.getOwnPropertySymbols){returnnames.concat(Object.getOwnPropertySymbols(events));}returnnames;};EventEmitter.prototype.eventNames:
/***Returnthenumberoflistenerslisteningtoagivenevent.**@param{(String|Symbol)}eventTheeventname.*@returns{Number}Thenumberoflisteners.*@public*/EventEmitter.prototype.listenerCount=functionlistenerCount(event){varevt=prefixprefix+event:event,listeners=this._events[evt];if(!listeners)return0;if(listeners.fn)return1;returnlisteners.length;};EventEmitter.prototype.listenerCount:
/***Addalistenerforagivenevent.**@param{(String|Symbol)}eventTheeventname.*@param{Function}fnThelistenerfunction.*@param{*}[context=this]Thecontexttoinvokethelistenerwith.*@returns{EventEmitter}`this`.*@public*/EventEmitter.prototype.on=functionon(event,fn,context){returnaddListener(this,event,fn,context,false);};EventEmitter.prototype.on:
/***Addaone-timelistenerforagivenevent.**@param{(String|Symbol)}eventTheeventname.*@param{Function}fnThelistenerfunction.*@param{*}[context=this]Thecontexttoinvokethelistenerwith.*@returns{EventEmitter}`this`.*@public*/EventEmitter.prototype.once=functiononce(event,fn,context){returnaddListener(this,event,fn,context,true);};EventEmitter.prototype.once:
/***Removealllisteners,orthoseofthespecifiedevent.**@param{(String|Symbol)}[event]Theeventname.*@returns{EventEmitter}`this`.*@public*/EventEmitter.prototype.removeAllListeners=functionremoveAllListeners(event){varevt;if(event){evt=prefixprefix+event:event;if(this._events[evt])clearEvent(this,evt);}else{this._events=newEvents();this._eventsCount=0;}returnthis;};EventEmitter.prototype.removeAllListeners:
////Aliasmethodsnamesbecausepeoplerolllikethat.//EventEmitter.prototype.off=EventEmitter.prototype.removeListener;EventEmitter.prototype.addListener=EventEmitter.prototype.on;