一、是什么

我们了解到,Node采用了事件驱动机制,而EventEmitter就是Node实现事件驱动的基础

EventEmitter的基础上,Node几乎所有的模块都继承了这个类,这些模块拥有了自己的事件,可以绑定/触发监听器,实现了异步操作

Node.js 里面的许多对象都会分发事件,比如 fs.readStream 对象会在文件被打开的时候触发一个事件

这些产生事件的对象都是 events.EventEmitter 的实例,这些对象有一个 eventEmitter.on() 函数,用于将一个或多个函数绑定到命名事件上

二、使用方法

Nodeevents模块只提供了一个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方法之后,然后实现onaddListenerprependListener这三个实例方法,都是添加事件监听触发函数,实现也是大同小异

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?的更多相关文章

  1. 简单剖析Node中的事件监听机制(一)

    使用js的class类简单的实现一个事件监听机制,不同于浏览器中的时间绑定与监听,类似于node中的时间监听,并且会在接下来的文章中去根据自己的理解去写一下Event模块中的原理. Node.js使用 ...

  2. node.js学习笔记(四)——EventEmitter

    error 事件 EventEmitter 定义了一个特殊的事件 error,它包含了错误的语义,我们在遇到异常的时候通常会触发 error 事件.当 error 被触发时,EventEmitter ...

  3. 实现一个EventEmitter类,这个类包含以下方法: on/ once/fire/off

    实现一个EventEmitter类,这个类包含以下方法: on(监听事件,该事件可以被触发多次)- once(也是监听事件,但只能被触发一次)- fire(触发指定的事件)- off(移除指定事件的某 ...

  4. node中的Stream-Readable和Writeable解读

    在node中,只要涉及到文件IO的场景一般都会涉及到一个类-Stream.Stream是对IO设备的抽象表示,其在JAVA中也有涉及,主要体现在四个类-InputStream.Reader.Outpu ...

  5. 利用Node.js的Net模块实现一个命令行多人聊天室

    1.net模块基本API 要使用Node.js的net模块实现一个命令行聊天室,就必须先了解NET模块的API使用.NET模块API分为两大类:Server和Socket类.工厂方法. Server类 ...

  6. 深入理解jQuery、Angular、node中的Promise

    最初遇到Promise是在jQuery中,在jQuery1.5版本中引入了Deferred Object,这个异步队列模块用于实现异步任务和回调函数的解耦.为ajax模块.队列模块.ready事件提供 ...

  7. Node中的定时器详解

    在大多数的业务中,我们都会有一些需求,例如几秒钟实现网页的跳转,几分钟对于后台数据进行清理,node与javascript都具有将代码延迟一段时间的能力.在node中可以使用三种方式实现定时功能:超时 ...

  8. [译]简单得不得了的教程-一步一步用 NODE.JS, EXPRESS, JADE, MONGODB 搭建一个网站

    原文: http://cwbuecheler.com/web/tutorials/2013/node-express-mongo/ 原文的源代码在此 太多的教程教你些一个Hello, World!了, ...

  9. node中的可读流和可写流

    javascript的一个不足之处是不能处理二进制数据,于是node中引入了Buffer类型.这个类型以一个字节(即8位)为单位,给数据分配存储空间.它的使用类似于Array,但是与Array又有不同 ...

  10. 学习node.js 第4篇 建立一个最小的web聊天系统

    我们生活在一个实时的世界里,有什么比聊天更加实时吗?那就让我们先写一个基于TCP 的聊天服务器吧,并且支持Telnet 连接.这很容易,而且能够完全用Node来编写.首先,我们需要在Node 中包含T ...

随机推荐

  1. RabbitMq 在centos中开机自启动

    1.在/etc/init.d 目录下新建一个 rabbitmq [root@localhost init.d]# vi rabbitmq 文件内容 #!/bin/bash #chkconfig:234 ...

  2. vue-cli-plugin-electron-builder

    https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/#installation 用cnpm安装 cnpm install ...

  3. vscode 格式化空格,constructor 构造函数的空格 会有问题,找到一个配置文件好使

    Ctrl+Shift+P "javascript.format.enable": false, "javascript.format.insertSpaceAfterCo ...

  4. python可视化工具pyecharts初相识

    一 概念 1.pyecahrts基础 某度开源了一个python的可视化工具pyecharts,该工具凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可.而 python 是一门富有表达力的语 ...

  5. 基于恒玄BES2600的轻量级鸿蒙操作系统AIOT开发平台解析之SDK下载和编译

    一 鸿蒙系统 华为鸿蒙系统是一款全新的面向全场景的分布式操作系统,创造一个超级虚拟终端互联的世界,将人.设备.场景有机地联系在一起, 将消费者在全场景生活中接触的多种智能终端实现极速发现.极速连接.硬 ...

  6. day02-事件处理机制

    5.Java事件处理机制 5.1小球移动案例 通过监听键盘按键,实现小球的移动 例子: package li.gui.even_; import javax.swing.*; import java. ...

  7. 手动将jar包安装到本地仓库并使用tomcat运行

    参考,欢迎点击原文:https://www.cnblogs.com/panchanggui/p/10696458.html 公司有个老项目是spring的,要我们自己本地安装,我发现我maven一直报 ...

  8. Python实现SQL注入脚本

    实验环境 攻击主机IP:172.18.53.145 目标主机IP:172.18.53.11 此处的靶场是Vulnhub中的WEB MACHINE: (N7) 靶场测试 访问靶场的登录页面,使用sqlm ...

  9. 记录--Object.assign 这算是深拷贝吗

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 在JavaScript中,Object.assign() 是一个用于合并对象属性的常见方法.然而,对于许多开发者来说,关于它是否执行深拷贝 ...

  10. 《Go程序设计语言》学习笔记之slice

    <Go程序设计语言>学习笔记之slice 一. 环境 Centos8.5, go1.17.5 linux/amd64 二. 概念 1) slice 表示一个拥有相同类型元素的可变长度的序列 ...