事件监听在前端的开发过程当中是一个很常见的状况。DOM上的事件监听方式,让咱们看到了经过事件的方式来进行具体的业务逻辑的处理的便捷。前端
在具体的一些业务场景中,第三方的自定义事件可以在层级较多,函数调用困难以及须要多个地方响应的时候有着其独特的优点——调用方便,避免多层嵌套,下降组件间耦合性。git
这篇文章所提到的EventEmitter3,就是一个典型的第三方事件库,可以让咱们经过自定义的实践来实现多个函数与组件间的通讯。github
EventEmitter3的设计较为的简单,具体结构能够看下图所示。api
下面咱们将按照通常人的正常思路来对这个结构进行介绍。数组
EE
function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; }
从类EE
的代码中咱们可以很明确的了解到,第一个参数为回调函数,第二个参数为回调函数的上下文,第三个参数是一个once
的标志位。因为代码简单,在这里就简单介绍下了。app
该方法用于存储eventEmitter的整个事件名称与回调函数的集合,初始值为undefined。函数
做用:返回当前已经注册的事件名称的列表学习
参数:无this
做用:返回某一个事件名称的全部监听函数spa
参数:event——事件名称,exists——是否只判断存在与否
做用:触发某个事件
参数:event——事件名,a1~a5——参数1~5
做用:为某个事件添加一个监听函数
参数:event——事件名,fn——回调函数,context——上下文
做用:相似on
,区别在于该函数只会触发一次
参数:event——事件名,fn——回调函数,context——上下文
做用:移除某个事件的监听函数
参数:event——事件名,fn——事件监听函数,context——只移除上下文匹配的事件监听函数,once——只移除类型匹配的事件监听函数
做用:移除某个时间的全部监听函数
参数:event——事件名
下面咱们将从添加监听函数, 事件触发与删除监听函数来进行具体的代码分析,从而了解该库的实现思路。
具体代码以下所示:
//一个单一的事件监听函数的单元 // // @param {Function} fn Event handler to be called. 回调函数 // @param {Mixed} context Context for function execution. 函数执行上下文 // @param {Boolean} [once=false] Only emit once 是否执行一次的标志位 // @api private 私有API function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; }
该类为eventEmitter中用于存储事件监听函数的最小类。
on
函数具体代码以下所示:
// Register a new EventListener for the given event. // 注册一个指定的事件的事件监听函数 // // @param {String} event Name of the event. 事件名 // @param {Function} fn Callback function. 回调函数 // @param {Mixed} [context=this] The context of the function. 上下文 // @api public 公有API EventEmitter.prototype.on = function on(event, fn, context) { var listener = new EE(fn, context || this) , evt = prefix ? prefix + event : event; if (!this._events) this._events = prefix ? {} : Object.create(null); if (!this._events[evt]) { this._events[evt] = listener;//第一次存储为一个事件监听对象 } else { if (!this._events[evt].fn) {//第三次及之后则直接向对象数组中添加事件监听对象 this._events[evt].push(listener); } else {//第二次将存储的对象与新对象转换为事件监听对象数组 this._events[evt] = [ this._events[evt], listener ]; } } return this; }
当咱们向事件E添加函数F时,会调用on
方法,此时on方法会检查eventEmitter中prototype属性events的E属性。
当这个属性为undefined
时,直接将该函数所在的事件对象赋值给evt属性。
当该属性当前值为一个对象且其函数fn不等于函数F时,则会将其转换为一个包含这两个事件对象的事件对象数组。
当这个属性已是一个对象数组时,则直接经过push
方法向数组中添加对象。
prefix
是用来判断Object.create()
方法是否存在,若是存在则直接调用该方法来建立属性,不然经过在属性前添加~
来避免覆盖原有属性。
once
的函数实现与on
函数基本一致,因此在此就再也不进行分析。
emit
函数代码以下所示:
// Emit an event to all registered event listeners. // 触发已经注册的事件监听函数 // // @param {String} event The name of the event. 事件名 // @returns {Boolean} Indication if we've emitted an event. 若是触发事件成功,则返回true,不然返回false // @api public 公有API EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event; if (!this._events || !this._events[evt]) return false; var listeners = this._events[evt] , len = arguments.length , args , i; if ('function' === typeof listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); switch(len) { case 1: return listeners.fn.call(listeners.context), true; case 2: return listeners.fn.call(listeners.context, a1), true; case 3: return listeners.fn.call(listeners.context, a1, a2), true; case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; } for(i = 1, args = new Array(len - 1); i < len; i++) { args[i - 1] = arguments[i]; } listeners.fn.apply(listeners.context, args); } else { //因为篇幅缘由省略E属性为数组时经过循环调用来实现事件触发的过程 } return true; };
当咱们触发事件E时,咱们只须要调用emit
方法。该方法会自动检索事件E中全部的事件监听对象,触发全部的事件监听函数,同时移除掉经过once
添加,只须要触发一次的事件监听函数。
removeListener
函数代码以下:
// Remove event listeners. // 移除事件监听函数 // // @param {String} event The event we want to remove. 须要被移除的事件名 // @param {Function} fn The listener that we need to find. 须要被移除的事件监听函数 // @param {Mixed} context Only remove listeners matching this context. 只移除匹配该参数指定的上下文的监听函数 // @param {Boolean} once Only remove once listeners. 只移除匹配该参数指定的once属性的监听函数 // @api public 公共API EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events || !this._events[evt]) return this; var listeners = this._events[evt] , events = []; if (fn) { if (listeners.fn) { if ( listeners.fn !== fn || (once && !listeners.once) || (context && listeners.context !== context) ) { events.push(listeners); } } else { //因为篇幅缘由省去便利listeners属性查找函数删除的代码 } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) { this._events[evt] = events.length === 1 ? events[0] : events; } else { delete this._events[evt]; } return this; };
removeListener
函数实现较为简单。当咱们须要移除事件E的某个函数时,它使用一个event
属性来保存不须要被移除的事件监听对象,而后便利整个事件监听数组(单个时为对象),而且最后将event
属性的值赋值给E属性从而覆盖掉原有的属性,达到删除的目的。
该库中还有一些其余的函数,因为对整个库的理解不产生太大影响,所以没有在此进行讲解,有须要的能够前往个人github仓库进行查看。
eventEmitter
的代码虽然结构清晰,可是仍然存在一些问题。例如on
和once
方法的实现中,只有一个属性不一样,其他代码都如出一辙,其实能够抽出一个特定的函数来进行处理,经过属性来进行区分调用便可。
同时,在同一个函数例如emit
中,也存在大量的重复代码,能够进行进一步的抽象和整理,使得代码更加简单。
eventEmitter
第三方事件库从实现上来看较为简单,而且结构清晰容易阅读,推荐有兴趣的能够花大约一个小时的时间来学习下。