进入 Node.js 的 events 模块以及 EventEmitter 的学习,并且实现它的底层逻辑。

events 模块属于 Node.js 服务端的知识,但是由于大多数 Node.js 核心API 构建用的是异步时间驱动架构

问题1:

EventEmitter 采用的是什么设计模式?

问题2:

EventEmitter 常用的API是怎样实现的?

Events 基本介绍

Node.js 的events 模块对外提供了一个 EventEmitter 对象,用于对Node.js 中的事件进行统一管理

在 EventEmitter 的基础上

Node.js 中几乎所有的模块都继承了这个类,以实现异步事件驱动架构

var events = require('events');
var eventEmitter = new events.EventEmitter();
eventEmitter.on('say', function(name) {
    console.log('Hello', name);
})
eventEmitter.emit('say', 'Jonh');

常用的 EventEmitter 模块的API

方法名 方法描述
addListener(event, listener) 为指定事件添加一个监听器到监听器数组的尾部
prependListener(event, listener) 与addListener 相对,为指定事件添加一个监听器到监听器数组的头部
on(event, listener) 其实就是 addListener 的别名
once(event, listener) 为指定事件注册一个单次监听器,即监听器最多只会触发一次,触发后立刻解除该监听器
removeListener(event, listener) 移除执行事件的某个监听器,监听器必须是该事件已经注册过的监听器
off(event, listener) removeListener的别名
removeAllListeners(event, listener) 移除所有事件的所有监听器,如果指定事件,则移除指定事件的所有监听器
setMaxListeners(n) 默认情况下,EventEmitter 中如果添加的监听器超过10个就会输出警告信息;setMaxListeners 函数用于提高监听器的默认限制的数量
listeners(event) 返回指定事件的监听器数组
emit(event, [arg1], [arg2], […]) 按参数的顺序执行每个监听器,如果事件有注册监听返回true,否则返回false

两个不需要手动添加的,额外特殊事件

事件名 事件描述
newListener 该事件在添加新事件监听器的时候触发
removeListener 从指定监听器数组中删除一个监听器,需要注意的是,此操作将会改变处于被删监听器之后的那些监听器的索引

addListener 和 removeListener、on 和 off 方法对比

addListener 方法的作用是为指定事件添加一个监听器,其实和 on 方法实现的功能是一样的

同时 removeListener 方法的作用是为移除某个事件的监听器

var events = require('events');
var emitter = new events.EventEmitter();
function hello1(name) {
    console.log('hello 1', name);
}
function hello2(name) {
    console.log('hello 2', name);
}
emitter.addListener('say', hello1);
emitter.addListener('say', hello2);
emitter.emit('say', 'John');
// 输出hello 1 John
// 输出hello 2 John
emitter.removeListener('say', hello1);
emitter.emit('say', 'John');
// 相应的,监听say事件的hello1事件被移除
// 只输出hello 2 John

removeListener 和 removeAllListeners

removeListener 方法是指移除一个指定事件的某一个监听器

removeAllListeners 指的是移除某一个指定事件的全部监听器

var events = require('events');
var emitter = new events.EventEmitter();
function hello1(name) {
    console.log('hello 1', name);
}
function hello2(name) {
    console.log('hello 2', name);
}
emitter.addListener('say', hello1);
emitter.addListener('say', hello2);
emitter.removeAllListeners('say');
emitter.emit('say', 'John');
// removeAllListeners 移除了所有关于 say 事件的监听
// 因此没有任何输出

on 和 once 方法区别

on方法:对于某一指定事件添加的监听器可以持续不断的监听相应的事件 once方法:添加的监听器,监听一次后,就会被消除

var events = require('events');
var emitter = new events.EventEmitter();
function hello(name) {
    console.log('hello', name);
}
emitter.on('say', hello);
emitter.emit('say', 'John');
emitter.emit('say', 'Lily');
emitter.emit('say', 'Lucy');
// 会输出 hello John、hello Lily、hello Lucy, 之后还要加也可以继续触发

emitter.once('see', hello);
emitter.emit('see', 'Tom');
// 只会输出一次 hello Tom

带你实现一个 EventEmitter

在浏览器端实现一个这样的 EventEmitter 是否可以呢?

自己封装一个能在浏览器中跑的 EventEmitter,它可以帮你实现自定义事件的订阅和发布,从而提升业务开发的便利性

function EventEmitter() {
    this.__events = {}
}
EventEmitter.VERSION = '1.0.0';
on 方法
EventEmitter.prototype.on = function(eventName, listener) {
    if (!eventName || !listener) return;
    // 判断回调的 listener 是否为函数
    if (!isValidListener(listener)) {
        throw new TypeError('listener must be a function');
    }
    var events = this.__events;
    var listeners = events[eventName] = events[eventName] || [];
    var listenerIsWrapped = typeof listener === 'object';
    // 不重复添加事件,判断是否有一样的
    if (indexOf(listeners, listener) === -1) {
        listeners.push(listenerIsWrapped ? listener : {
            listener: listener,
            once: false
        });
    }
    return this;
};

// 判断是否是合法的 listener
function isValidListener(listener) {
    if (typeof listener === 'function') {
        return true;
    } else if (listener && typeof listener === 'object') {
        return isValidListener(listener.listener);
    } else {
        return false;
    }
}

// 顾名思义,判断新增自定义事件是否存在
function indexOf(array, item) {
    var result = -1;
    item = typeof item === 'object' ? item.listener : item;

    for (var i = 0, len = array.length; i < len; i++) {
        if (array[i].listener === item) {
            result = i;
            break;
        }
    }
    return result;
}
emit 和 off 方法
EventEmitter.prototype.emit = function(eventName, args) {
    // 直接通过内部对象获取对应自定义事件的回调函数
    var listeners = this.__events[eventName];
    if (!listeners) return;
    // 需要考虑多个listener 的情况
    for (var i = 0; i < listeners.length; i++) {
        var listener = listeners[i];
        if (listener) {
            listener.listener.apply(this, args || []);
            // 给 listener 中 once 为 true 的进行特殊处理
            if (listener.once) {
                this.off(eventName, listener.listener)
            }
        }
    }
    return this;
}

EventEmitter.prototype.off = function(eventName, listener) {
    var listeners = this.__events[eventName];
    if (!listeners) return;

    var index;
    for (var i = 0, len = listeners.length; i < len; i++) {
        if (listeners[i] && listeners[i].listener === listener) {
            index = i;
            break;
        }
    }
    // off 的关键
    if (typeof index !== 'undefined') {
        listeners.splice(index, 1, null)
    }
    return this;
}
once 和 allOff 方法
EventEmitter.prototype.once = function(eventName, listener) {
    // 直接调用 on 方法,once 参数传入 true,待执行之后进行 once 处理
    return this.on(eventName, {
        listener: listener,
        once: true
    })
}

EventEmitter.prototype.allOff = function(eventName) {
    // 如果该 eventName 存在,则将其对应的 listener 的数组直接清空
    if (eventName && this.__events[eventName]) {
        this.__events[eventName] = []
    } else {
        this.__events = []
    }
};

总结

EventEmitter 采用的正是发布-订阅模式

发布-订阅模式其实是观察者模式的一种变形

区别在于:发布-订阅模式在观察者模式的基础上,在目标和观察者之间增加了一个调度中心

在 Vue 框架中不同组件之间的通讯里,有一种解决方案叫做 EventBus

和 EventEmitter 的思路类似,它的基本用途是将 EventBus 作为组件传递数据的桥梁,所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所有组件都可以收到通知,使用起来非常便利,其核心其实就是发布-订阅模式的落地实现。