资讯专栏INFORMATION COLUMN

【node不完全指西】EventEmitter (事件发布/订阅模式)解析

yagami / 1070人阅读

摘要:从异步编程解决方案说起吧事件发布订阅模式模式流程控制库事件发布订阅模式事件监听器模式是一种广泛运用于异步编程的模式,是回调函数的事件话,又称发布订阅模式。

从node异步编程解决方案说起吧:

事件发布/订阅模式

Promise/deferred模式

流程控制库

事件发布/订阅模式
事件监听器模式是一种广泛运用于异步编程的模式,是回调函数的事件话,又称发布/订阅模式。

主要实现的几个功能包括

on

remove

once

emit

废话少说,我们来简单的实现一个事件监听函数吧

首先创建一个eventEmitter函数
function EventEmitter() {
    // 用Object.create(null)代替空对象{}
    // 好处是无杂质,不继承原型链
    // _events来保存观察着队列的信息
    this._events = Object.create(null);
}

因为过多的侦听器占用大量内存,导致内存泄漏,所以侦听器的个数一般不会超过10个,否则会有warnning警告⚠️
接下来是一些默认的设置

// 默认最多的绑定次数
EventEmitter.defaultMaxListeners = 10;
// 同on方法
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
// 返回监听的事件名
EventEmitter.prototype.eventNames = function () {
    return Object.keys(this._events);
};
// 设置最大监听数
EventEmitter.prototype.setMaxListeners = function (n) {
    this._count = n;
};
// 返回监听数
EventEmitter.prototype.getMaxListeners = function () {
    return this._count ? this._count : this.defaultMaxListeners;
};
接下来是on函数的实现
EventEmitter.prototype.on = function (type, cb, flag) {
    // 不是newListener 就应该让newListener执行以下
    if (type !== "newListener") {
        this._events["newListener"] && this._events["newListener"].forEach(listener => {
            listener(type);
        });
    }
    if (this._events[type]) {
        // 根据传入的flag来决定是向前还是向后添加
        if (flag) {
            this._events[type].unshift(cb);
        } else {
            this._events[type].push(cb);
        }
    } else {
        this._events[type] = [cb];
    }
    // 监听的事件不能超过了设置的最大监听数
    if (this._events[type].length === this.getMaxListeners()) {
        console.warn("警告-监听器Number过大");
    }
};

解析:
on函数是帮定的初始函数,首先判断是否是首次进行侦听,如果是的话,先进行一遍初始化函数
接下来在——events队列里找到指针为type的地方,根据flag判断是在队列尾还是头加入callback函数

接下来是once监听一次的实现方法
// 监听一次
EventEmitter.prototype.once = function (type, cb, flag) {
    // 先绑定,调用后删除
    function wrap() {
        cb(...arguments);
        this.removeListener(type, wrap);
    }
    // 自定义属性
    wrap.listen = cb;
    this.on(type, wrap, flag);
};

解析:
实现为在callback上包装一层remove操作,再当做一个新的callback传入on函数
这样的的话在首次执行回调的时候就会执行remove操作,达到执行一次就删除的操作

接下来是remove函数,删除一个type的侦听器
EventEmitter.prototype.removeListener = function (type, cb) {
    if (this._events[type]) {
        this._events[type] = this._events[type].filter(listener => {
            return cb !== listener && cb !== listener.listen;
        });
    }
};

解析:
传入type和要删除的callback,对type标记的数组进行 filter操作,假如cb cb === listener则过滤掉

删除所有
EventEmitter.prototype.removeAllListener = function () {
    this._events = Object.create(null);
};
接下来是发布函数 emit
EventEmitter.prototype.emit = function (type, ...args) {
    if (this._events[type]) {
        this._events[type].forEach(listener => {
            listener.call(this, ...args);
        });
    }
};

解析:
也比较直观,如果events里面存在type的监听器队列,则队列里的每个回调都执行一遍,并且用call函数绑定this和arg

完整代码
//EventEmitter.js


function EventEmitter() {
    // 用Object.create(null)代替空对象{}
    // 好处是无杂质,不继承原型链的东东
    this._events = Object.create(null);
}
// 默认最多的绑定次数
EventEmitter.defaultMaxListeners = 10;
// 同on方法
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
// 返回监听的事件名
EventEmitter.prototype.eventNames = function () {
    return Object.keys(this._events);
};
// 设置最大监听数
EventEmitter.prototype.setMaxListeners = function (n) {
    this._count = n;
};
// 返回监听数
EventEmitter.prototype.getMaxListeners = function () {
    return this._count ? this._count : this.defaultMaxListeners;
};
// 监听
EventEmitter.prototype.on = function (type, cb, flag) {
    // 默认值,如果没有_events的话,就给它创建一个
    if (!this._events) {
        this._events = Object.create(null);
    }
    // 不是newListener 就应该让newListener执行以下
    if (type !== "newListener") {
        this._events["newListener"] && this._events["newListener"].forEach(listener => {
            listener(type);
        });
    }
    if (this._events[type]) {
        // 根据传入的flag来决定是向前还是向后添加
        if (flag) {
            this._events[type].unshift(cb);
        } else {
            this._events[type].push(cb);
        }
    } else {
        this._events[type] = [cb];
    }
    // 监听的事件不能超过了设置的最大监听数
    if (this._events[type].length === this.getMaxListeners()) {
        console.warn("警告-警告-警告");
    }
};
// 向前添加
EventEmitter.prototype.prependListener = function (type, cb) {
    this.on(type, cb, true);
};
EventEmitter.prototype.prependOnceListener = function (type, cb) {
    this.once(type, cb, true);
};
// 监听一次
EventEmitter.prototype.once = function (type, cb, flag) {
    // 先绑定,调用后删除
    function wrap() {
        cb(...arguments);
        this.removeListener(type, wrap);
    }
    // 自定义属性
    wrap.listen = cb;
    this.on(type, wrap, flag);
};
// 删除监听类型
EventEmitter.prototype.removeListener = function (type, cb) {
    if (this._events[type]) {
        this._events[type] = this._events[type].filter(listener => {
            return cb !== listener && cb !== listener.listen;
        });
    }
};
EventEmitter.prototype.removeAllListener = function () {
    this._events = Object.create(null);
};
// 返回所有的监听类型
EventEmitter.prototype.listeners = function (type) {
    return this._events[type];
};
// 发布
EventEmitter.prototype.emit = function (type, ...args) {
    if (this._events[type]) {
        this._events[type].forEach(listener => {
            listener.call(this, ...args);
        });
    }
};

module.exports = EventEmitter;

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/dev...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103329.html

相关文章

  • Node 之 Event 模块

    摘要:为什么把叫做集合而不能称为严格意义上的对象,来看这个集合的构造函数可以见得,是与处于同一层级的而非是继承自,所以说由实例出来的对象更加的纯净,并没有诸如等方法,更像是一个集合。 写在前面 事件的编程方式具有轻量级、松耦合、只关注事务点等优势,在浏览器端,有着自己的一套DOM事件机制,其中含包括这诸如事件冒泡,事件捕获等;然而Node的事件机制没有事件冒泡等,其原理就是设计模式中的观察者...

    mrli2016 评论0 收藏0
  • 从观察者模式到手写EventEmitter源码

    摘要:观察者模式观察者模式广泛的应用于语言中,浏览器事件如鼠标单击,键盘事件都是该模式的例子。可以看到,这就是观察者模式的订阅方法实现。小结通过创建可观察的对象,当发生一个感兴趣的事件时可将该事件通告给所有观察者,从而形成松散的耦合。 观察者模式 观察者模式(observer)广泛的应用于javascript语言中,浏览器事件(如鼠标单击click,键盘事件keyDown)都是该模式的例子。...

    cocopeak 评论0 收藏0
  • 解析nodeJS模块源码 亲手打造基于ES6的观察者系统

    摘要:为指定事件注册一个单次监听器,即监听器最多只会触发一次,触发后立刻解除该监听器。移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。返回指定事件的监听器数组。如何创建空对象我们已经了解到,是要来储存监听事件监听器数组的。 毫无疑问,nodeJS改变了整个前端开发生态。本文通过分析nodeJS当中events模块源码,由浅入深,动手实现了属于自己的ES6事件观察者系统。千万不...

    csRyan 评论0 收藏0
  • JavaScript 发布-订阅模式

    摘要:发布订阅模式订阅者把自己想订阅的事件注册到调度中心,当发布者发布该事件到调度中心,也就是该事件触发时,由调度中心统一调度订阅者注册到调度中心的处理代码。 发布-订阅模式,看似陌生,其实不然。工作中经常会用到,例如 Node.js EventEmitter 中的 on 和 emit 方法;Vue 中的 $on 和 $emit 方法。他们都使用了发布-订阅模式,让开发变得更加高效方便。 一...

    13651657101 评论0 收藏0
  • EventEmitter的实现

    摘要:实例方法的话,最核心的就是分别是添加事件,删除事件,发布事件。为了防止进程崩溃,可以在对象的事件上注册监听器,或使用模块。注意,模块已被废弃。作为最佳实践,应该始终为事件注册监听器。 前言 事件在js中非常的常见,不管是浏览器还是node,这种事件发布/订阅模式的应用都是很常见的。至于发布/订阅模式和观察者模式是否是同一种设计模式说法都有,这里不做具体的讨论。在之前的项目中也曾自己实现...

    CoreDump 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<