Node中的EventEmitter? 如何实现一个EventEmitter?

一、是什么
我们了解到,Node采用了事件驱动机制,而EventEmitter就是Node实现事件驱动的基础
在EventEmitter的基础上,Node几乎所有的模块都继承了这个类,这些模块拥有了自己的事件,可以绑定/触发监听器,实现了异步操作
Node.js 里面的许多对象都会分发事件,比如 fs.readStream 对象会在文件被打开的时候触发一个事件
这些产生事件的对象都是 events.EventEmitter 的实例,这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上
二、使用方法
Node的events模块只提供了一个EventEmitter类,这个类实现了Node异步事件驱动架构的基本模式——观察者模式
在这种模式中,被观察者(主体)维护着一组其他对象派来(注册)的观察者,有新的对象对主体感兴趣就注册观察者,不感兴趣就取消订阅,主体有更新的话就依次通知观察者们
基本代码如下所示:
const EventEmitter = require('events')
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter()
function callback() {
console.log('触发了event事件!')
}
myEmitter.on('event', callback)
myEmitter.emit('event')
myEmitter.removeListener('event', callback);
通过实例对象的on方法注册一个名为event的事件,通过emit方法触发该事件,而removeListener用于取消事件的监听
关于其常见的方法如下:
- emitter.addListener/on(eventName, listener) :添加类型为 eventName 的监听事件到事件数组尾部
- emitter.prependListener(eventName, listener):添加类型为 eventName 的监听事件到事件数组头部
- emitter.emit(eventName[, ...args]):触发类型为 eventName 的监听事件
- emitter.removeListener/off(eventName, listener):移除类型为 eventName 的监听事件
- emitter.once(eventName, listener):添加类型为 eventName 的监听事件,以后只能执行一次并删除
- emitter.removeAllListeners([eventName]):移除全部类型为 eventName 的监听事件
三、实现过程
通过上面的方法了解,EventEmitter是一个构造函数,内部存在一个包含所有事件的对象
class EventEmitter {
constructor() {
this.events = {};
}
}
其中events存放的监听事件的函数的结构如下:
{
"event1": [f1,f2,f3],
"event2": [f4,f5],
...
}
然后开始一步步实现实例方法,首先是emit,第一个参数为事件的类型,第二个参数开始为触发事件函数的参数,实现如下:
emit(type, ...args) {
this.events[type].forEach((item) => {
Reflect.apply(item, this, args);
});
}
当实现了emit方法之后,然后实现on、addListener、prependListener这三个实例方法,都是添加事件监听触发函数,实现也是大同小异
on(type, handler) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(handler);
}
addListener(type,handler){
this.on(type,handler)
}
prependListener(type, handler) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].unshift(handler);
}
紧接着就是实现事件监听的方法removeListener/on
removeListener(type, handler) {
if (!this.events[type]) {
return;
}
this.events[type] = this.events[type].filter(item => item !== handler);
}
off(type,handler){
this.removeListener(type,handler)
}
最后再来实现once方法, 再传入事件监听处理函数的时候进行封装,利用闭包的特性维护当前状态,通过fired属性值判断事件函数是否执行过
once(type, handler) {
this.on(type, this._onceWrap(type, handler, this));
}
_onceWrap(type, handler, target) {
const state = { fired: false, handler, type , target};
const wrapFn = this._onceWrapper.bind(state);
state.wrapFn = wrapFn;
return wrapFn;
}
_onceWrapper(...args) {
if (!this.fired) {
this.fired = true;
Reflect.apply(this.handler, this.target, args);
this.target.off(this.type, this.wrapFn);
}
}
完整代码如下:
class EventEmitter {
constructor() {
this.events = {};
}
on(type, handler) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(handler);
}
addListener(type,handler){
this.on(type,handler)
}
prependListener(type, handler) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].unshift(handler);
}
removeListener(type, handler) {
if (!this.events[type]) {
return;
}
this.events[type] = this.events[type].filter(item => item !== handler);
}
off(type,handler){
this.removeListener(type,handler)
}
emit(type, ...args) {
this.events[type].forEach((item) => {
Reflect.apply(item, this, args);
});
}
once(type, handler) {
this.on(type, this._onceWrap(type, handler, this));
}
_onceWrap(type, handler, target) {
const state = { fired: false, handler, type , target};
const wrapFn = this._onceWrapper.bind(state);
state.wrapFn = wrapFn;
return wrapFn;
}
_onceWrapper(...args) {
if (!this.fired) {
this.fired = true;
Reflect.apply(this.handler, this.target, args);
this.target.off(this.type, this.wrapFn);
}
}
}
测试代码如下:
const ee = new EventEmitter();
// 注册所有事件
ee.once('wakeUp', (name) => { console.log(`${name} 1`); });
ee.on('eat', (name) => { console.log(`${name} 2`) });
ee.on('eat', (name) => { console.log(`${name} 3`) });
const meetingFn = (name) => { console.log(`${name} 4`) };
ee.on('work', meetingFn);
ee.on('work', (name) => { console.log(`${name} 5`) });
ee.emit('wakeUp', 'xx');
ee.emit('wakeUp', 'xx'); // 第二次没有触发
ee.emit('eat', 'xx');
ee.emit('work', 'xx');
ee.off('work', meetingFn); // 移除事件
ee.emit('work', 'xx'); // 再次工作
参考文献
- http://nodejs.cn/api/events.html#events_class_eventemitter
Node中的EventEmitter? 如何实现一个EventEmitter?的更多相关文章
- 简单剖析Node中的事件监听机制(一)
使用js的class类简单的实现一个事件监听机制,不同于浏览器中的时间绑定与监听,类似于node中的时间监听,并且会在接下来的文章中去根据自己的理解去写一下Event模块中的原理. Node.js使用 ...
- node.js学习笔记(四)——EventEmitter
error 事件 EventEmitter 定义了一个特殊的事件 error,它包含了错误的语义,我们在遇到异常的时候通常会触发 error 事件.当 error 被触发时,EventEmitter ...
- 实现一个EventEmitter类,这个类包含以下方法: on/ once/fire/off
实现一个EventEmitter类,这个类包含以下方法: on(监听事件,该事件可以被触发多次)- once(也是监听事件,但只能被触发一次)- fire(触发指定的事件)- off(移除指定事件的某 ...
- node中的Stream-Readable和Writeable解读
在node中,只要涉及到文件IO的场景一般都会涉及到一个类-Stream.Stream是对IO设备的抽象表示,其在JAVA中也有涉及,主要体现在四个类-InputStream.Reader.Outpu ...
- 利用Node.js的Net模块实现一个命令行多人聊天室
1.net模块基本API 要使用Node.js的net模块实现一个命令行聊天室,就必须先了解NET模块的API使用.NET模块API分为两大类:Server和Socket类.工厂方法. Server类 ...
- 深入理解jQuery、Angular、node中的Promise
最初遇到Promise是在jQuery中,在jQuery1.5版本中引入了Deferred Object,这个异步队列模块用于实现异步任务和回调函数的解耦.为ajax模块.队列模块.ready事件提供 ...
- Node中的定时器详解
在大多数的业务中,我们都会有一些需求,例如几秒钟实现网页的跳转,几分钟对于后台数据进行清理,node与javascript都具有将代码延迟一段时间的能力.在node中可以使用三种方式实现定时功能:超时 ...
- [译]简单得不得了的教程-一步一步用 NODE.JS, EXPRESS, JADE, MONGODB 搭建一个网站
原文: http://cwbuecheler.com/web/tutorials/2013/node-express-mongo/ 原文的源代码在此 太多的教程教你些一个Hello, World!了, ...
- node中的可读流和可写流
javascript的一个不足之处是不能处理二进制数据,于是node中引入了Buffer类型.这个类型以一个字节(即8位)为单位,给数据分配存储空间.它的使用类似于Array,但是与Array又有不同 ...
- 学习node.js 第4篇 建立一个最小的web聊天系统
我们生活在一个实时的世界里,有什么比聊天更加实时吗?那就让我们先写一个基于TCP 的聊天服务器吧,并且支持Telnet 连接.这很容易,而且能够完全用Node来编写.首先,我们需要在Node 中包含T ...
随机推荐
- 遥感图像镶嵌拼接:ENVI的Pixel Based Mosaicking工具操作方法
本文介绍基于ENVI软件,利用"Pixel Based Mosaicking"工具实现栅格遥感影像镶嵌拼接的方法. 首先需要说明的是,本文需要镶嵌的遥感影像并不含地理参考信 ...
- vite + vue3 打包后 本地直接运行 type="module" crossorigin 替换为defer - 多个vue文件就不好使了
vite + vue3 打包后 本地直接运行 type="module" crossorigin 替换为defer 需求: 想打包后,双击运行,不启动服务 修改 vite.conf ...
- 图片裁剪插件 vue-cropper [vue插件推荐]
一个优雅的图片裁剪插件 https://www.npmjs.com/package/vue-cropper http://github.xyxiao.cn/vue-cropper/example/
- snippet n. 小片, 片断, 摘录 单词记忆
snippet 助记:snip[剪断] + -et小词后缀. 关键是 snip 怎么记忆 snip : 拟声词,模仿剪断东西的声音. 助记单词 slip 滑 根据字母 n是done的缩写,可以 想成 ...
- 写了一个 dict.cn 的油猴脚本,目的是通过url进行搜索。这样就能配合wox进行单词的搜索了。
写了一个 dict.cn 的油猴脚本,目的是通过url进行搜索.这样就能配合wox进行单词的搜索了. // ==UserScript== // @name dict.cn // @namespace ...
- 【刷题】LeetCode 239 滑动窗口最大值- Python手撕最大堆
手撕版 最大堆的完全实现, 堆中元素为二元组(num, idx),比较时用数值,赋值或交换时用整个元组. class Heap: def __init__(self, arr, capacity): ...
- MinimalApis自动注册
前言 在Asp.Net Core 6 推出了最小 Api(MinimalApis)来简化WebApi的开发,在前后端分离的趋势下越来越多的后端服务只提供Api接口,但是用Controller的开发模式 ...
- grails中使用render方法输出XML总结
GrailsXMLJava 1.首先看看简单的XML生成情况 Java代码 收藏代码 <images> <image title="one" /& ...
- python学习笔记(2): 函数式编程
函数式编程 高阶函数 map() map接受一个函数和一个可迭代对象(Iterable),返回一个map对象(Iterator) def f(x): return x*x list(map(f, [1 ...
- uni-app攻略:如何对接驰腾打印机
一.引言 在当前的移动开发生态中,跨平台框架如uni-app因其高效.灵活的特点受到了开发者们的青睐.同时,随着物联网技术的飞速发展,智能打印设备已成为许多业务场景中不可或缺的一环.今天,我们就来探讨 ...