NodeJS 事件系统详解

版权声明:CopyRight 2016, Heley Chen, Follow CC BY-NC-SA License https://blog.csdn.net/zccz14/article/details/51463715

NodeJS 事件系统是很多其它NodeJS系统(如net、http(s)等)的基础之一。

事件系统可以定义自己的一套事件系统,并绑定若干处理事件。

事件系统封装在NodeJS的 events 包里,具体API与官方说明参见官方文档


基本使用姿势

引入 events 包

var events = require('events');

这个 events 是一个类(JS中类即函数),它还有几个成员,其中EventEmitter 这个类跟events类是一样的,这样写的目的是为了跨版本兼容(Node 自身的 API 版本变动)。

events === events.EventEmitter; // true

事件发射器(EventEmitter) 就是一个管理一组事件的对象。

var myEvent = new events(); // create a new event emitter object

触发事件

触发事件使用emitter.emit(eventName: String[, ...argv: Any ]); 来触发,顺便可以在这里传入任意个数、格式不限的参数。

class Person {
    constructor (name) { 
        this.name = name; 
    }
    destroy () {
        console.log(this.name, 'dead');
    }
}
var Riki = new Person('Riki');
myEvent.on('go-die', person => person.destroy());
myEvent.emit('go-die', Riki); // Riki dead

为事件绑定监听器

有的人喜欢称呼这个动作为”绑定事件”,然而这个说法并不好,因为每个事件名可以对应若干监听器(listener),准确的说应该是为事件绑定监听器,或者是将监听器绑定到事件上。

一次性监听器

一次性监听器在绑定后,只能被触发一次,触发后就自我销毁,下次再触发同样的事件也不会再响应。

使用emitter.once(eventName: String, listener: Function); 将listener绑定到对应的事件上。

myEvent.once('init', () => { sum = 0; });
myEvent.emit('init'); // sum == 0;
sum ++; // sum == 1;
myEvent.emit('init'); // sum == 1;

常态监听器

与一次性监听器不同,常态监听器在触发后不会销毁,每一次触发事件时都会调用。

使用emitter.on(eventName: String, listener: Function); 将listener绑定到对应的事件上。

myEvent.on('init', () => { sum = 0; });
myEvent.emit('init'); // sum == 0;
sum ++; // sum == 1;
myEvent.emit('init'); // sum == 0;

监听器列表

可以在一个事件上绑定若干监听器,每一个事件都会对应一个监听器列表,当事件触发时依次阻塞执行:按照监听器的顺序,一个一个执行,当上一个监听器返回结果后,再执行下一个监听器(同步: Synchronous)。

常态监听器与一次性监听器可以混合绑定到同一个事件上。

使用on, once绑定顺序与执行顺序一致:先绑定的先执行,后绑定的后执行。

利用prependListener、prependOnceListener分别对应on、once,区别在于prepend*方法将监听器插入到监听器列表的开头,优先执行。

异步监听器

监听器列表的同步阻塞执行是不可改变的,要实现异步的监听器只能用异步方法做一些妥协。

  • setImmediate 立即执行
  • setInterval 周期循环执行
  • setTimeout 延迟执行
myEvent.on('fire', () => {
    setTimeout(() => {
        console.log('fired');
    }, 1000); // 秒射
});
myEvent.emit('fire'); // 1秒后输出 fired

移除监听器

清空所有监听器

使用emitter.removeAllListeners() 方法来清空所有事件的所有监听器。

清空若干事件的所有监听器

使用emitter.removeAllListeners([eventNames])

移除某事件的某监听器

如果你还保存着监听器的引用,那么你可以使用 emitter.removeListener(eventName, listener) 来精确移除监听器。

如果你没有保存监听器的引用,其实事件发射器已经帮你保存好了(不然它怎么调用呢)。
使用emitter._events[eventNames] 来获取事件对应的监听器(列表)。

完整样例:事件驱动的生物种群模拟

var events = require('events');
var myEvent = new events();

class Creature {
    constructor() {
        this.age = 0;
        this.alive = true;
    }
}
// 主循环
myEvent.on('start', cluster => {
    setInterval(() => {
        myEvent.emit('time', cluster);
    }, 1000);
});

// 报告生存
myEvent.on('time', cluster => {
    var nowAlive = cluster.filter(v => v.alive);
    console.log(`new year come! now alive ${nowAlive.length} creatures`);
});

// 成长或死亡
myEvent.on('time', cluster => {
    cluster.filter(v => v.alive).forEach((v, i) => {
        if (~~(Math.random() * 10) < 5) myEvent.emit('dead', v);
        else myEvent.emit('grow', v);
    });
});

// 繁衍
myEvent.on('time', cluster => {
    var times = cluster.filter(v => v.alive).length;
    for (var i = 0; i < times; i++) cluster.push(new Creature());
})

// 检查终止
myEvent.on('time', cluster => {
    if (cluster.filter(v => v.alive).length == 0) myEvent.emit('end', cluster);
});

// 结算
myEvent.on('end', cluster => {
    console.log(`all ${cluster.length} creatures dead`);
    console.log(`dead age summary:${cluster.map(v => v.age).reduce((a, b) => a + b, 0)}`);
    process.exit(0);
})

// 成长
myEvent.on('grow', creature => { creature.age++; });
// 死亡
myEvent.on('dead', creature => { creature.alive = false; });

var cluster = [new Creature(), new Creature()];
myEvent.emit('start', cluster);

EventEmitter 内部结构

属性

以下划线开头的属性通常是库内部使用,不希望对外开放的属性,因此最好不要试图去修改这些属性。

console.log(myEvent);
/**
EventEmitter {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: {},
  _eventsCount: 0,
  _maxListeners: undefined 
}
*/

可以看到一个事件发射器实例中有domain, _events, _eventsCount, _maxListeners 这几个可枚举属性。

domain是域,在原先的版本中是独立于events包的一个包,最近NodeJS将domain并入events,并且废弃单独的domain包,在官方文档中 domain包的稳定级别已经降为0,很有可能在下一个大版本中就会消失。
主要是domain的功能基本就是events功能的子集,因此domain就被并入了events。大体上一个domain也就是一个EventEmitter。

_events

_events 是一个关联数组(eventName -> listener([]))。

比如在事件a上绑定了两个监听器,在b上绑定了一个监听器时:

_events: {
  a: [ [Function], [Function] ],
  b: [Function]
}

一旦某事件的监听器数量超过1个,便会变成监听器列表。

_eventsCount

代表一共有多少事件是有监听器的。

使用时最好使用emitter.eventNames().length 代替。

_maxListeners

代表每个事件中最大的监听器数量。

说是这么说,但当真的超过这个数量的时候,系统只会 console.log 一个 warning 提示你监听器数量超了。

多出来的监听器依然能够正确地插入监听器列表,并正确执行。

getMaxListeners()setMaxListeners(n)_maxListeners 属性的 getter 与 setter。

阅读更多
换一批

没有更多推荐了,返回首页