1+1=10

扬长避短 vs 取长补短

Node.js 学习笔记(五)

接前面

继续了解Node.js。看看events模块

引言

Nodejs手册中介绍说:

Node.js核心API的很大一部分是基于一种惯用的异步事件驱动架构构建的,其中某些类型的对象(称为“emitters”)会发出命名事件,从而调用函数对象(“listeners”)。

功能类似的三个类:

  • EventEmitter,Nodejs中事件发射类
  • EventTarget,Nodejs中仿WebAPI接口类
  • NodeEventTarget,Nodejs将EventTarget封装成类似EventEmitter的接口

EventEmitter

事件模块的核心类是 EventEmitter,我觉得可以先和Qt信号槽对比一下:

特性/概念 EventEmitter (Node.js) QObject (Qt)
模块 内置模块 (events) 核心库( QtCore )
实例化 const emitter = new EventEmitter(); QObject 的派生类的实例化
添加监听器 on()addListener() connect()
发射 emit() 调用信号函数,一个空的 emit宏
一次性监听 once() connect() 使用Qt::UniqueConnection
移除监听器 off()removeListener()removeAllListeners() disconnect()
传递参数 通过 emit 方法传递参数给监听器 通过信号的参数列表传递数据给槽函数
自定义事件 灵活 派生QObject 增加信号

通过on()来添加事件监听器,我们在标准输入处理,文件系统模块fs,各种网络模块中的例子中都已经反复在用了。但是只是照猫画虎,需要稍微多了解一点点。

不妨,通过若干简短的例子来学习一下

初识 EventEmitter

建立自定义事件(mySignal)和监听器的连接,而后 emit自定义事件:

import {EventEmitter} from 'node:events';

const myEmitter = new EventEmitter();
myEmitter.on('mySignal', () => {
  console.log('Hi 1+1=10, your event mySignal received!');
});

myEmitter.emit('mySignal');

// output:
// Hi 1+1=10, your event mySignal received!

可以多次emit,以多次触发监听器:

import {EventEmitter} from 'node:events';

const myEmitter = new EventEmitter();
myEmitter.on('mySignal', () => {
  console.log('Hi 1+1=10, your event mySignal received!');
});

myEmitter.emit('mySignal');
myEmitter.emit('mySignal');

// output:
// Hi 1+1=10, your event mySignal received!
// Hi 1+1=10, your event mySignal received!

如果多次emit,只想响应一次,只需要使用once()而不是on()

import {EventEmitter} from 'node:events';

const myEmitter = new EventEmitter();
myEmitter.once('mySignal', () => {
  console.log('Hi 1+1=10, your event mySignal received!');
});

myEmitter.emit('mySignal');
myEmitter.emit('mySignal');

// output:
// Hi 1+1=10, your event mySignal received!

如果多次建立连接,回调会被调用同样次数:

import {EventEmitter} from 'node:events';

const myEmitter = new EventEmitter();
const myCallback = () => {
  console.log('Hi 1+1=10, your event mySignal received!');
};

myEmitter.on('mySignal', myCallback);
myEmitter.on('mySignal', myCallback);

myEmitter.emit('mySignal');

// output:
// Hi 1+1=10, your event mySignal received!
// Hi 1+1=10, your event mySignal received!

传递参数

传递参数简单,直接就是函数参数:

import {EventEmitter} from 'node:events';

const myEmitter = new EventEmitter();
myEmitter.on('mySignal', (a, b) => {
  console.log(`Hi 1+1=10, your event mySignal with ${a} ${b} received!`);
});

myEmitter.emit('mySignal', 1, 2);

// output:
// Hi 1+1=10, your event mySignal with 1 2 received!

如果要使用 this的话,注意不要用箭头函数:

import {EventEmitter} from 'node:events';

const myEmitter = new EventEmitter();
myEmitter.on('mySignal', function (a, b) {
    console.log(a, b, myEmitter == this);
});

myEmitter.on('mySignal', (a, b) => {
    console.log(a, b, myEmitter == this);
});

myEmitter.emit('mySignal', 1, 2);

// output:
// 1 2 true
// 1 2 false

EventTarget 与 Event

Nodejs提供了仿WebAPI中的EventTarget,Event、CustomEvent。EventTarget不是EventEmitter派生类,接口简单:

  • eventTarget.addEventListener()
  • eventTarget.dispatchEvent()
  • eventTarget.removeEventListener()

Node.js 的 EventTarget与Web API的EventTarget 有所不同:

  • 在 Node.js 中,没有层次结构或事件传播的概念。分发到 EventTarget 的事件不会自动在嵌套的目标对象层次结构中传播。每个 EventTarget 独立处理其自己的处理程序集。
  • 在 Node.js 中,如果事件监听器是异步函数或返回 Promise,并且该 Promise 被拒绝,拒绝将自动捕获并与同步抛出的监听器一样处理。

例子

简单例子:

const myTarget = new EventTarget();
myTarget.addEventListener('mySignal', (event) => {
    console.log(event);
    console.log("mySignal received");
});

myTarget.dispatchEvent(new Event('mySignal', {}));

输出结果:

Event {
  type: 'mySignal',
  defaultPrevented: false,
  cancelable: false,
  timeStamp: 25.34469997882843
}
mySignal received

要传递参数的话,用CustomEvent而不是Event

const myTarget = new EventTarget();
myTarget.addEventListener('mySignal', (event) => {
    console.log(event.detail);
    console.log("mySignal received");
});

myTarget.dispatchEvent(new CustomEvent('mySignal', {detail: '1+1=10'}));

//output:
// 1+1=10
// mySignal received

所谓CustomEvent,也是标准定义好的。提供了detail成员。

NodeEventTarget

nodejs从EventTarget派生出 NodeEventTarget,以模仿EventEmitter的接口。

但是奇怪,文档少的可怜,我连一个小的demo都凑不齐(跑不通)

// import NodeEventTarget from 'node-event-target';

const myEmitter = new NodeEventTarget();
myEmitter.on('mySignal', (a, b) => {
  console.log(`Hi 1+1=10, your event mySignal with ${a} ${b} received!`);
});

myEmitter.emit('mySignal', 1, 2);

// expected output:
// Hi 1+1=10, your event mySignal with 1 2 received!

查看源码,发现其在文件 lib/internal/event_target.js内:

class NodeEventTarget extends EventTarget {
  static [kIsNodeEventTarget] = true;
  static defaultMaxListeners = 10;

  constructor() {
    super();
    initNodeEventTarget(this);
  }
  // ...

还是不知道怎么用,以后慢慢看...

参考

  • https://nodejs.org/api/events.html
  • https://dom.spec.whatwg.org/#event

Comments