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('嗨', '你好');
输入结果如图:
实现思路:
我们先将特定事件的监听器最大设置为常量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 模块有进一步的理解。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍简单实现Node的Events模块?相关面试题,主要包含被问及简单实现Node的Events模块?时的应答技巧和注意事项,需要的朋友参考一下 参考回答: 简介:观察者模式或者说订阅模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。 node中的Events模块就是通过观察者模式来实现的: 这样,eve
本文向大家介绍最通俗易懂的javascript变量提升详解,包括了最通俗易懂的javascript变量提升详解的使用技巧和注意事项,需要的朋友参考一下 如下所示: 在我没有讲什么是变量提升,以及变量提升的规则之前, 或者你没有学习过变量提升,如果按照现有的javascript理解, 对于上述的例子,你可能会认为第3行代码的输出结果应该是undefined, 因为第二行是var a; 声明变量,但是
上一篇 21、Kafka为什么那么快? Cache Filesystem Cache PageCache缓存 顺序写 由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。 Zero-copy 零拷技术减少拷贝次数 Batching of Messages 批量量处理。合并小的请求,然后以流的方式进行交互,直顶网络上限。 Pull 拉模式 使用拉模式进行消息的获取消费,
本文向大家介绍通俗易懂的C#之反射教程,包括了通俗易懂的C#之反射教程的使用技巧和注意事项,需要的朋友参考一下 前言 之所以要写这篇关于C#反射的随笔,起因有两个: 第一个是自己开发的网站需要用到 其次就是没看到这方面比较好的文章。 所以下定决心自己写一篇,废话不多说开始进入正题。 前期准备 在VS2012中新建一个控制台应用程序(我的命名是ReflectionStudy),这个项目是基于.net
本文向大家介绍详解node child_process模块学习笔记,包括了详解node child_process模块学习笔记的使用技巧和注意事项,需要的朋友参考一下 NodeJs是一个单进程的语言,不能像Java那样可以创建多线程来并发执行。当然在大部分情况下,NodeJs是不需要并发执行的,因为它是事件驱动性永不阻塞。但单进程也有个问题就是不能充分利用CPU的多核机制,根据前人的经验,可以通过
本文向大家介绍JavaScript模块模式实例详解,包括了JavaScript模块模式实例详解的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了JavaScript模块模式。分享给大家供大家参考,具体如下: 在JS中没有Class的概念,那么如何体现Object的Public和Private属性呢,答案就是模块模式(Module Pattern)。 JS中有一个显著的特性: 匿名函数(ano