事件的编程方式具备轻量级、松耦合、只关注事务点等优点,在浏览器端,有着本身的一套DOM
事件机制,其中含包括这诸如事件冒泡,事件捕获等;然而Node
的事件机制没有事件冒泡等,其原理就是设计模式中的观察者模式。Node
不少的模块继承这个事件模块,下面咱们就来根据源码来学习下其API
,作到知其然更知其因此然。node
const EventEmitter = require("events"); const EventEmitter = require("events").EventEmitter;
常常会看到这种两种方式来引入咱们的events
模块,可是在Node
的高版本中能够直接使用第一种方式,高版本也支持下面这种方式,下面的这种方式主是在Node
的0.10.x
版本时使用,源码中也是很清楚,之因此这么作就是为了兼容低版本时写下的Node
代码:git
module.exports = EventEmitter; EventEmitter.EventEmitter = EventEmitter;
注:以后提到的obj
为EventEmitter
的实例对象,也就是obj = new EventEmitter()
github
获得咱们的事件构造函数后,咱们就能够来实例化一个事件对象:编程
const EventEmitter = require("events"), follow = new EventEmitter(); follow.on("node", question => { console.log(`有一个关于node的问题: ${question}`); }); follow.emit("node", "jade与ejs选择哪一个?");
这是一个简单的使用,下面咱们就来看看咱们所用到的API
以及它们的实现:设计模式
on(type, listener)
来订阅事件,传入的type
参数为事件名,listener
为待发布函数。同时addListener
方法和on
方法有着一样的效果,指向的是内存的同一块:数组
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
在调用on
时,倘若咱们订阅了newListener
事件,该事件会先被发布。浏览器
那么问题来了?订阅的事件被存储在了哪里呢?app
答案就是obj._events
,这是一个事件集合,事件名就是该集合的键名,当事件的待补发函数只有一个时,键值为函数;当有多个时,键值为数组。为何把obj._events
叫作集合而不能称为严格意义上的对象,来看这个集合的构造函数:函数
function EventHandlers () {} EventHandlers.prototype = Object.create(null);
能够见得,EventHandlers.prototype
是与Object.prototype
处于同一层级的而非是继承自Object.prototype
,因此说由EventHandlers
实例出来的对象更加的"纯净",并无诸如toString
等方法,更像是一个集合。学习
随着给一个事件添加待发布函数,当添加的数量超过10条是,会发现有警告:
(node) warning: possible EventEmitter memory leak detected. 11 git listeners added. Use emitter.setMaxListeners() to increase limit.
产生警告的缘由就是事件待发布函数数组的长度超过了默认的最大容量,默认的最大容量是EventEmitter.defaultMaxListeners
,而这个属性是一个getter/setter
访问器,访问的是变量defaultMaxListeners
的值,也就是10。
// 获得最大容量 function $getMaxListeners (that) { if (that._maxListeners === undefined) return EventEmitter.defaultMaxListeners; return that._maxListeners; } // 发出警告代码: if (!existing.warned) { m = $getMaxListeners(target); if (m && m > 0 && existing.length > m) { existing.warned = true; process.emitWarning('Possible EventEmitter memory leak detected. ' + `${existing.length} ${type} listeners added. ` + 'Use emitter.setMaxListeners() to increase limit'); } }
观察得到最大容量函数能够发现,给obj._maxListeners
赋值能够提高咱们的最大容量(obj._maxListeners
初始化时被赋值为undefined
),能够利用setMaxListeners(n)
方法来进行赋值:
EventEmitter.prototype.setMaxListeners = function (n) { if (typeof n !== "number" || n < 0 || isNaN(n)) throw new TypeError("n must be a position number"); this._maxListeners = n; return this; };
看源码能够发现,订阅事件实际上是用的_addListener
函数,其最后一个参数为prepend
,表明着是否将待发布函数添加到事件数组的第一个,因此应该还有一个prependListener(type, listener)
函数,能够将listener
添加到obj.events.type
的第一个位置。
once(type, listener)
,经过这种方式添加的待发布函数,只能被发布一次,发布一次后就会被移除。
// 将listener包裹在一个g函数中,在每次执行时,现将该函数从事件数组中移除 // 真正的待发布函数成为了g函数的属性 function _onceWrap (target, type, listener) { var fired = false; function g () { target.removeListener(type, g); // 先移除 if (!fired) { fired = true; listener.apply(target, arguments); // 再发布 } } g.listener = listener; return g; }
于此对应的还有prependOnceListener
方法,下面来看一个例子:
work.once("git", pull); work.on("git", () => { console.log("git status"); }); work.emit("git"); console.log("第二次"); work.emit("git"); // git pull // git status // 第二次 // git status
emit(type, args)
来进行事件的发布,在实现上也很简单就是执行obj.events.type
的函数或者遍历obj.events.type
数组一次执行函数,须要注意的是error
事件的发布,若是没有订阅error
事件的话,发布时,就会用到throw new Error()
。
removeListener(type, listener)
来移除${type}
事件的listener
函数
removeAllListeners(type)
,当传入type
时会将type
事件所有移除;不传入参数时,会将obj._events
重置。
在移除时,倘若给obj
订阅了removeListener
事件的话,那么在每移除一个待发布函数时,会发布一次该事件,在将obj
重置时,也会最后将该事件移除。
function pull () { console.log("git pull"); }; work.on("removeListener", (type, fn) => { console.log(`remove ${type} ${fn.name} event`); }) work.on("git", pull); work.on("git", () => { console.log("git status"); }); work.removeListener("git", pull); work.emit("git"); // 依次输出 // remove git pull event // git status
eventNames
,返回实例对象全部的事件名数组或者一个空数组,源码中利用了ES6
的新方法Reflect.ownKeys
来得到obj._events
对象的自身属性:
EventEmitter.prototype.eventNames = function eventNames() { return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; };
listenerCount(type)
返回事件的待发布函数的的数量,也就是obj._events.length or 1
,这个方法在obj
上和EventEmitter
都有,本质上都是调用下面这个方法,实现也是很明了:
function listenerCount (type) { const events = this._events; if (events) { const evlistener = events[type]; if (typeof evlistener === "function") { return 1; } else if (evlistener) { return evlistener.length; } } }
listeners(type)
,返回${type}
事件的待发布函数数组或者空数组,须要注意是这个数组并非obj.events.type
的引用。
此次阅读Node的源代码发现,Node源码中对于原生的slice
和splice
并无使用,而是本身写了一个针对性更增强的arrayClone
和spliceOne
函数,不知这样写的缘由是否是要将速度提高,由于看V8源码会发现,slice
和splice
的实现有一些复杂,都有额外的判断来对参数进行规范化,像Node源码本身写的话,减小了这些无用的判断,从而提高了效率。固然这只是我我的的一些理解,若有错误还请你们指出。