详解如何模拟实现node中的Events模块(通俗易懂版)


Posted in Javascript onApril 15, 2019

Nodejs 的大部分核心 API 都是基于异步事件驱动设计的,事件驱动核心是通过 node 中 Events 对象来实现事件的发送和监听回调绑定,我们常用的 stream 模块也是依赖于 Events 模块是来实现数据流之间的回调通知,如在数据到来时触发 data 事件,流对象为可读状态触发 readable 事件,当数据读写完毕后发送 end 事件。

既然 Events 模块如此重要,我们有必要来学习一下 Events 模块的基本使用,以及如何模拟实现 Events 模块中常用的 api

一、Events 模块的基本使用以及简单实现

首先我们了解一下 Events 模块的基本用法,其实 Events 模块本质上是观察者模式的实现,所谓观察者模式就是:

它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知

观察者模式有对应的观察者以及被观察的对象,在 Events 模块中,对应的实现就是 on 和 emit 函数

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('嗨', (str) => {
 console.log(str);
});
myEmitter.emit('嗨','你好');

从上述的使用中,我们可以知道 on 是用来监听事件的发生,而 emit 是用来触发事件的发生,一旦 emit 触发了事件,on 就会被通知到,从而执行对应的回调函数。

有了这个实例,我们可以思考下如何实现这个 EventEmitter 类。

思路:当我们执行 on 函数时,我们可以将回调函数保存起来,等到 emit 触发了事件时,将回调函数拿出来执行,那么就可以实现了事件的监听以及订阅了。

class EventEmitter{
 constructor(){
  #事件监听函数保存的地方
  this.events={};
 }
 on(eventName,listener){
  if (this.events[eventName]) {
   this.events[eventName].push(listener);
  } else {
   #如果没有保存过,将回调函数保存为数组
   this.events[eventName] = [listener];
  }
 }
 emit(eventName){
  #emit触发事件,把回调函数拉出来执行
  this.events[eventName] && this.events[eventName].forEach(listener => listener())
 }
}

上述就实现了一个简单的 EventEmitter 类,下面来实例一下:

let event = new EventEmitter();
event.on('嗨',function(){
 console.log('你好');
});
event.emit('嗨');
#输出:你好

完善:我们注意到在原生的 EventEmitter 类中,emit 是可以传递参数到我们的回调函数中,那么我们实现的类也应该支持传递参数。我们对 emit 进行如下更改

emit(eventName,...rest){
 #emit触发事件,把回调函数拉出来执行
 this.events[eventName] && this.events[eventName].forEach(listener => listener.apply(this,rest))
}

完善之后,重新实例化,如下:

let event = new EventEmitter();
event.on('嗨',function(str){
 console.log(str);
});
event.emit('嗨','你好');
#输出:你好

二、Events 模块中常用的 api

Events 模块中除了 on、emit 函数之外,还包含了很多常用的 api,我们一一来介绍几个实用的 api

API名称 API方法描述
addListener(eventName, listener) on(eventName, listener)别名,为指定事件添加一个监听器到监听器数组的尾部
removeListener(eventName, listener) 从名为 eventName 的事件的监听器数组中移除指定的 listener
removeAllListeners(eventName, listener) 移除全部监听器或指定的 eventName 事件的监听器
once(eventName, listener) 添加单次监听器 listener 到名为 eventName 的事件
listeners(eventName) 返回名为 eventName 的事件的监听器数组的副本
setMaxListeners(n) 可以为指定的 EventEmitter 实例修改监听器数量限制

1. addListener 与 on 方法使用与实现

在 Events 模块中,addListener 与 on 方法的使用是完成相同的,只是名字不同,我们可以通过原型来给两个函数建立相等关联

EventEmitter.prototype.addListener=EventEmitter.prototype.on

2. removeListener 与 off 方法使用与实现

removeListener 方法可以从指定名字的监听器数组中移除指定的 listener,这样的话,当再次 emit 事件的时候,不会触发 on 绑定的回调函数,如下:

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
let callback = (str) => {
 console.log(str);
}
myEmitter.on('嗨', callback);
myEmitter.emit('嗨','你好');#输出:你好
myEmitter.removeListener('嗨',callback);
myEmitter.emit('嗨','你好');#无输出

实现思路:我们只要在执行 removeListener 函数的时候,将先前保存的回调函数去除掉即可

removeListener (eventName,listener) {
 #保证回调函数数组存在,同时去除指定的listener
 this.events[eventName] && this.events[eventName] = this.events[eventName].filter(l => l != listener);
}

同时 removeListener 与 off 方法也是功能完全相同,只是命名不同,因此可以通过如下方法赋值:

EventEmitter.prototype.removeListener=EventEmitter.prototype.off

3. removeAllListeners 方法使用与实现

removeAllListeners 移除全部监听器或指定的 eventName 事件的监听器,其实 removeAllListeners 就包含了 removeListener 的功能,只是 removeListener 只能指定特定的监听器,removeAllListeners 可以移除全部监听器。

实现思路:在执行 removeAllListeners,将所有的回调函数都给去除即可

removeAllListeners (eventName) {
 #移除全部监听器
 delete this.events[eventName]
}

4. once 方法使用与实现

once 方法的描述是添加单次监听器 listener 到名为 eventName 的事件,其实就是通过 once 添加的监听器,只能执行一次,执行一次之后就会被销毁,不能再次执行

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.once('嗨', (str) => {
 console.log(str);
});
myEmitter.emit('嗨','你好');
myEmitter.emit('嗨','你好');
myEmitter.emit('嗨','你好'); #只能输出一次你好

实现思路:当 once 监听的事件回调函数执行之后,通过 removeListener 将事件监听器给解绑掉,那么事件再次被 emit 的时候,就不会再次执行回调,这样就能保证事件回调只能执行一次

once (eventName, listener) {
 #重新改变监听回调函数,使其执行之后可以被销毁
 let reListener = (...rest) => {
  listener.apply(this,rest);
  #执行完之后解除事件绑定
  this.removeListener(type,wrapper);
 }
 this.on(eventName,reListener);
}

5. listeners 方法使用与实现

listeners 方法返回名为 eventName 的事件的监听器数组的副本,其实就是获取 eventName 中所有的回调函数,这个实现起来很容易,就不多赘述了,代码如下:

listeners (eventName) {
 return this.events[eventName]
}

6. setMaxListeners 方法使用与实现

默认情况下,如果为特定事件添加了超过 10 个监听器,则 EventEmitter 会打印一个警告。 这有助于发现内存泄露, 但是,并不是所有的事件都要限制 10 个监听器。 emitter.setMaxListeners() 方法可以为指定的 EventEmitter 实例修改限制。 值设为 Infinity(或 0)表示不限制监听器的数量。

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
let callback = (str) => {
 console.log(str);
}
for (let i = 0; i <= 11; i++) {
 myEmitter.on('嗨', callback);
}
myEmitter.emit('嗨', '你好');

输入结果如图:

详解如何模拟实现node中的Events模块(通俗易懂版)

实现思路:

我们先将特定事件的监听器最大设置为常量10

constructor(){
 #事件监听函数保存的地方
 this.events={};
 #最大监听器数量
 this._maxListeners = 10;
}

然后在我们的 on 函数中,对这个监听器的数量进行判断,从而作出提示

on(eventName,listener){
 if (this.events[eventName]) {
  this.events[eventName].push(listener);
  #如果超过最大限度,以及不为0,则作出内存泄漏提示
  if (this._maxListeners != 0 && this.events[type].length >= this._maxListeners) {
   console.error('超过最大的监听数量可能会导致内存泄漏');
  }
 } else {
  #如果没有保存过,将回调函数保存为数组
  this.events[eventName] = [listener];
 }
}

我们也支持对 _maxListeners 变量根据用户的输入进行更改,即我们的 setMaxListeners() 函数

setMaxListeners(MaxListeners) {
 this._maxListeners = MaxListeners
}

三、总结

本文从 node 的 Events 模块出发,然后去介绍了 Events 模块常用 API 的使用,从中通过一步一步简易去思考这些 API 使用的内部原理,简易的实现了这些 API,希望大家看完文章之后,能对 Events 模块有进一步的理解。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
firefox浏览器下javascript 拖动层效果与原理分析代码
Dec 04 Javascript
关于flash遮盖div浮动层的解决方法
Jul 17 Javascript
jquery tab插件精简版分享
Sep 10 Javascript
javascript中window.event事件用法详解
Dec 11 Javascript
JS实现div居中示例
Apr 17 Javascript
Javascript Objects详解
Sep 04 Javascript
jquery小火箭返回顶部代码分享
Aug 19 Javascript
判断jQuery是否加载完成,没完成继续判断的解决方法
Dec 06 jQuery
详解axios中封装使用、拦截特定请求、判断所有请求加载完毕)
Apr 09 Javascript
Layui实现数据表格中鼠标悬浮图片放大效果,离开时恢复原图的方法
Sep 11 Javascript
纯JS实现五子棋游戏
May 28 Javascript
纯JS开发baguetteBox.js响应式画廊插件
Jun 28 Javascript
小程序数据通信方法大全(推荐)
Apr 15 #Javascript
前端面试知识点目录一览
Apr 15 #Javascript
详解vuex持久化插件解决浏览器刷新数据消失问题
Apr 15 #Javascript
vue-cli项目使用mock数据的方法(借助express)
Apr 15 #Javascript
说说Vuex的getters属性的具体用法
Apr 15 #Javascript
vue 中Virtual Dom被创建的方法
Apr 15 #Javascript
详解jQuery中的getAll()和cleanData()
Apr 15 #jQuery
You might like
在Windows XP下安装Apache+MySQL+PHP环境
2015/02/22 PHP
基于ThinkPHP实现批量删除
2015/12/18 PHP
基于laravel制作APP接口(API)
2016/03/15 PHP
Laravel中间件实现原理详解
2016/10/09 PHP
PHP更安全的密码加密机制Bcrypt详解
2017/06/18 PHP
php实现的中秋博饼游戏之绘制骰子图案功能示例
2017/11/06 PHP
Jquery焦点与失去焦点示例应用
2014/06/10 Javascript
js设置控件的隐藏与显示的两种方法
2014/08/21 Javascript
基于d3.js实现实时刷新的折线图
2016/08/03 Javascript
jQuery Ajax使用FormData对象上传文件的方法
2016/09/07 Javascript
JavaScript模拟文件拖选框样式v1.0的实例
2017/08/04 Javascript
js实现加载页面就自动触发超链接的示例
2017/08/31 Javascript
微信小程序App生命周期详解
2018/01/31 Javascript
vue 中swiper的使用教程
2018/05/22 Javascript
vue+webpack中配置ESLint
2018/11/07 Javascript
浅谈layui使用模板引擎动态渲染元素要注意的问题
2019/09/14 Javascript
vue项目配置同一局域网可使用ip访问的操作
2020/10/23 Javascript
微信小程序之高德地图多点路线规划过程示例详解
2021/01/18 Javascript
关于Js中new操作符的作用详解
2021/02/21 Javascript
Python和perl实现批量对目录下电子书文件重命名的代码分享
2014/11/21 Python
python遍历一个目录,输出所有的文件名的实例
2018/04/23 Python
pytorch torchvision.ImageFolder的用法介绍
2020/02/20 Python
keras模型保存为tensorflow的二进制模型方式
2020/05/25 Python
结束运行python的方法
2020/06/16 Python
pytorch 常用函数 max ,eq说明
2020/06/28 Python
matplotlib之pyplot模块之标题(title()和suptitle())
2021/02/22 Python
CSS3实现曲线阴影和翘边阴影
2016/05/03 HTML / CSS
html5使用Canvas绘图的使用方法
2017/11/21 HTML / CSS
HTML5几个设计和修改的页面范例分享
2015/09/29 HTML / CSS
成龙洗发水广告词
2014/03/14 职场文书
平安建设工作方案
2014/06/02 职场文书
村党的群众路线教育实践活动总结材料
2014/10/31 职场文书
早上好问候语大全
2015/11/10 职场文书
PHP实现创建以太坊钱包转账等功能
2021/04/21 PHP
OpenCV-Python实现轮廓拟合
2021/06/08 Python
CSS作用域(样式分割)的使用汇总
2021/11/07 HTML / CSS