EventEmitter 是 NodeJS 的核心模块 events
中的类,用于对 NodeJS 中的事件进行统一管理,用 events
特定的 API 对事件进行添加、触发和移除等等,核心方法的模式相似于发布订阅。数组
function EventEmitter() {
this._events = Object.create(null);
}
/*
* 其余方法
*/
// 导出自定义模块
module.export = EventEmitter;复制代码
在构造函数 EventEmitter 上有一个属性 _events
,类型为对象,用于存储和统一管理全部类型的事件,在建立构造函数的时候导出了 EventEmitter,后面实现其余方法的代码将放在构造函数与导出中间。bash
在 EventEmitter 中监听的每一类事件都有最大监听个数,超过了这个数值,事件虽然能够正常执行,可是会发出警告信息,其目的是为了防止内存泄露。tcp
EventEmitter.defaultMaxListeners = 10;复制代码
这个同类型事件最大个数默认是 10
,EventEmitter 固然也有方法设置和获取这个值,下面是设置和获取同类型事件最大监听个数的方法实现。函数
// 设置同类型事件监听最大个数
EventEmitter.prototype.setMaxListeners = function (count) {
this._count = count;
}
// 获取同类型事件监听最大个数
EventEmitter.prototype.getMaxListeners = function () {
return this._count || EventEmitter.defaultMaxListeners;
}复制代码
在设置这个值的时候其实就是给 EventEmitter 实例添加了一个 _count
的属性用来存储设置的新值来做为这个类型事件的最大监听个数,在获取的时候就是获取 _count
,若是没有设置过就获取默认值。ui
在给 EventEmitter 的实例添加事件监听时,在 _event
对象中会以事件的类型做为属性名,值为一个数组,每次添加这个类型事件的时候,会将要执行的函数存入这个数组中进行统一管理。this
添加事件监听的方法有 on
、once
、addListener
、prependListener
和 prependOnceListener
:spa
on
等同于 addListener
将函数正常添加到 _event
对应事件类型的数组中;once
将函数添加到 _event
对应事件类型的数组中,可是只能执行一次;prependListener
将函数添加到 _event
对应事件类型的数组中的前面;prependOnceListener
将函数添加到 _event
对应事件类型的数组中的前面,但只能执行一次。在 EventEmitter 中正常添加事件有四点须要注意:
一、若是其余的类使用 util
模块的 inherits
方法继承 EventEmitter 时是没法继承实例属性的,在调用操做 _events
的方法中由于没法获取到 _events
致使报错,为了兼容这种继承的状况,在获取不到 _events
时应添加一个 _events
到继承 EventEmitter 的类的实例上;
二、若是添加事件的类型为 newListener
,传入要执行的函数会有一个参数 type
,是事件的类型,以后再添加事件的时候,就会执行 newListener
的函数,对添加的事件的事件类型进行处理;
三、on
方法表面上有两个参数,实际上有第三个参数,为布尔值,表明是否从 _events
对应事件类型的数组前面追加函数成员;
四、在添加事件的时候须要判断是否超出这个类型事件的最大监听个数,若是超出要打印警告信息。prototype
on 方法和 addListener 方法的实现:code
// 添加事件监听
EventEmitter.prototype.on = EventEmitter.prototype.addListener = function (type, callback, flag) {
// 兼容继承不存在 _events 的状况
if (!this._events) this._events = Object.create(null);
// 若是 type 不是 newListener 就去执行 newListener 的回调
if (type !== "newListener") {
// 若是没添加过 newListener 事件就忽略此处的逻辑
if (this._events["newListener"] && this._events["newListener"].length) {
this._events["newListener"].forEach(fn => fn(type));
}
}
// 若是不是第一次添加 callback 存入数组中
if (this._events[type]) {
// 是否从数组前面添加 callback
if (flag) {
this._events[type].unshift(callback);
} else {
this._events[type].push(callback);
}
} else {
// 第一次添加,在 _events 中建立数组并添加 callback 到数组中
this._events[type] = [callback];
}
// 获取事件最大监听个数
let maxListeners = this.getMaxListeners();
// 判断 type 类型的事件是否超出最大监听个数,超出打印警告信息
if (this._events[type].length - 1 === maxListeners) {
console.error(`MaxListenersExceededWarning: ${maxListeners + 1} ${type} listeners added`);
}
}复制代码
经过上面代码能够看出 on
方法的第三个参数实际上是服务于 prependListener
方法的,其余添加事件的方法都是基于 on
来实现的,只是在调用 on
的外层作了不一样的处理,而咱们平时调这些添加事件监听的方法时都只传入 type
和 callback
。对象
prependListener 方法的实现:
// 添加事件监听,从数组的前面追加
EventEmitter.prototype.prependListener = function (type, callback) {
// 第三个参数为 true 表示从 _events 对应事件类型的数组前面添加 callback
this.on(type, callback, true);
}复制代码
once 方法的实现:
// 添加事件监听,只能执行一次
EventEmitter.prototype.once = function (type, callback, flag) {
let wrap => (...args) {
callback(...args);
// 执行 callback 后当即从数组中移除 callback
this.removeListener(type, wrap);
}
// 存储 callback,确保单独使用 removeListener 删除传入的 callback 时能够被删除掉
wrap.realCallback = callback;
// 调用 on 添加事件监听
this.on(type, wrap, flag);
}复制代码
想让事件只执行一次,须要在执行 callback
以后就当即在数组中移除这个函数,因为是同步执行,直接操做 callback
是很难实现的,添加事件其实就是添加 callback
到 _events
对应类型的数组中,咱们在使用 once
的时候将 callback
包一层函数名为 wrap
,将这个外层函数存入数组,wrap
的内部逻辑就是真正 callback
的调用和移除 wrap
,这里涉及到事件监听的移除方法 removeListener
在后面来详细说明。
once
的第三个参数是为了 prependOnceListener
服务的,prependOnceListener
与 prependListener
实现方式相似,不一样的是 prependOnceListener
是基于 once
实现的。
prependOnceListener 方法的实现:
// 添加事件监听,从数组的前面追加,只执行一次
EventEmitter.prototype.prependOnceListener = function (type, callback) {
// 第三个参数为 true 表示从 _events 对应事件类型的数组前面添加 callback
this.once(type, callback, true);
}复制代码
移除事件监听有两个方法,分别是 removeListener
和 removeAllListeners
,前者的做用是移除某个类型数组中的某个回调函数,后者的做用是移除某个类型数组的全部成员,若是类型参数为空,则清空整个 _events
。
removeListener 方法的实现:
// 移除事件执行程序
EventEmitter.prototype.removeListener = function (type, callback) {
if(this._events[type]) {
// 过滤掉当前传入的要移除的 callback
this._events[type] = this._events[type].filter(fn => {
return fn !== callback && fn !== callback.realCallback;
});
}
}复制代码
因为 once
中在真正的 callback
包了一层 wrap
, 只有在触发事件时才能执行 wrap
并执行 removeListener
删掉函数,若是在事件触发以前使用 removeListener
删除,传入的是真正的回调 callback
,没法删除,因此在 once
方法中对真正的 callback
进行了存储,在 removeListener
中调用 filter
时的返回条件的逻辑中作了处理。
removeAllListeners 方法的实现:
// 移除所有事件执行程序
EventEmitter.prototype.removeAllListeners = function (type) {
// 存在 type 清空 _events 对应的数组,不然直接清空 _events
if (type) {
this._events[type] = [];
} else {
this._events = Object.create(null);
}
}复制代码
执行事件就比较简单了,取出 _events
中对应类型的数组进行循环,执行内部的每个函数,第一个参数为 type
,后面参数会做为数组中函数执行传入的参数。
// 触发事件
EventEmitter.prototype.emit = function (type, ...args) {
if (this._events[type]) {
// 循环执行函数,并将 this 指回 EventEmitter 实例
this._events[type].forEach(fn => fn.call(this, ...args));
}
}复制代码
// 获取监听的全部事件类型
EventEmitter.prototype.eventNames = function () {
return Object.keys(this._events);
}复制代码
// 获取事件类型对应的数组
EventEmitter.prototype.listeners = function (type) {
return this._events[type];
}复制代码
EventEmitter 的核心逻辑已经实现,因为上面大多数方法须要组合使用,因此在没有一一验证,下面让咱们经过一些案例来了解 EventEmitter 的用法。
咱们在这里引入本身自定义的 events
模块,并使用 util
模块的 inherits
继承 EventEmitter,下面是前置代码,后面将不在重复。
// 引入依赖
const EventEmitter = require("./events");
const util = require("util");
function Girl() {}
// 使 Girl 继承 EventEmitter
util.inherits(Girl, EventEmitter);
// 建立 Girl 的实例
let girl = new Girl();复制代码
案例 1:设置和获取同类型事件的最大监听个数
// 获取事件最大监听个数
console.log(girl.getMaxListeners()); // 10
// 设置事件最大监听个数
girl.setMaxListeners(2);
console.log(girl.getMaxListeners()); // 2复制代码
案例 2:使用 on 添加事件并执行
girl.on("失恋", () => console.log("哭了"));
girl.on("失恋", () => console.log("喝酒"));
girl.emit("失恋");
// 哭了
// 喝酒复制代码
案例 3:使用 prependListener 添加事件并执行
girl.on("失恋", () => console.log("哭了"));
girl.prependListener("失恋", () => console.log("喝酒"));
girl.emit("失恋");
// 喝酒
// 哭了复制代码
案例 4:添加 newListener 类型的事件
girl.on("newListener", (type) => console.log(type));
girl.on("失恋", () => console.log("哭了"));
girl.on("和好", () => console.log("开心"));
// 失恋
// 和好复制代码
案例 5:添加同类型事件超出最大个数并执行事件
// 设置事件最大监听个数
girl.setMaxListeners(2);
girl.on("失恋", () => console.log("哭了"));
girl.on("失恋", () => console.log("喝酒"));
girl.on("失恋", () => console.log("吸烟"));
girl.emit("失恋");
// MaxListenersExceededWarning: 3 失恋 listeners added
// 哭了
// 喝酒
// 吸烟复制代码
案例 6:对比 on 和 once
girl.on("失恋", () => console.log("哭了"));
girl.once("失恋", () => console.log("喝酒"));
girl.emit("失恋");
girl.emit("失恋");
// 哭了
// 喝酒
// 哭了复制代码
案例 7:移除 on 和 once 添加的事件监听
let cry = () => console.log("哭了");
let drink = () => console.log("喝酒");
girl.on("失恋", cry);
girl.once("失恋", drink);
girl.on("失恋", () => console.log("吸烟"));
girl.removeListener("失恋", cry);
girl.removeListener("失恋", drink);
// 吸烟复制代码
案例 8:使用 prependOnceListener 添加事件监听
girl.on("失恋", () => console.log("哭了"));
girl.prependOnceListener("失恋", () => console.log("喝酒"));
girl.emit("失恋");
girl.emit("失恋");
// 喝酒
// 哭了
// 哭了复制代码
案例 9:获取某个事件类型执行程序的集合
let cry = () => console.log("哭了");
let drink = () => console.log("喝酒");
girl.on("失恋", cry);
girl.once("失恋", drink);
girl.once("失恋", () => console.log("吸烟"));
console.log(girl.listeners("失恋"));
// [ [Function: cry], [Function: drink], [Function] ]复制代码
案例 10:获取全部事件类型名称
girl.on("失恋", () => console.log("哭了"));
girl.on("和好", () => console.log("开心"));
console.log(girl.eventNames());
// [ '失恋', '和好' ]复制代码
案例 11:使用 removeAllListeners 按类型移除事件监听
girl.on("失恋", () => console.log("哭了"));
girl.on("失恋", () => console.log("喝酒"));
girl.on("和好", () => console.log("开心"));
// 移除 “失恋” 类型事件监听
girl.removeAllListeners("失恋");
console.log(girl.listeners("失恋"));
// []复制代码
案例 12:使用 removeAllListeners 移除所有事件监听
girl.on("失恋", () => console.log("哭了"));
girl.on("失恋", () => console.log("喝酒"));
girl.on("和好", () => console.log("开心"));
// 移除所有事件监听
girl.removeAllListeners();
console.log(girl._events);
// {}复制代码
events
模块在 NodeJS 中的使用率很是高,不少其余模块的事件执行机制都是经过继承该模块的 EventEmitter
类来实现的,好比 ReadStream
(可读流)、WriteStream
(可写流)、net
(tcp)和 http
等等,咱们也能够经过上面案例的方式建立本身的类去继承 EventEmitter
来实现事件的管理。