手写一个node中的发布订阅

1,909 阅读6分钟

大家好,又见面了,我就是上篇文章说滚去写业务代码,然后被产品折磨到如今才有点空闲时间就立马爬来写文章的热爱学习和知识的程序员(不断句,一气呵成,自己甚至觉得有点小骄傲),好了,转入正题,上次我们聊了聊关于事件环的知识,其中我们提到了一点是说node中的api大部分都是通过回调函数和发布订阅的模式来进行的,ok,划个重点,发布订阅!这就是我们今天文章的主题,也是整个node学习中较为重要的一部分,今天就带大家来手写一个node中的发布订阅~

发布订阅

之前我们聊过发布订阅的模式就是,当一个用户订阅了一些事件之后,如果这些事件触发了,那我们就要去执行用户在订阅事件的时候定义的函数。

简单的举个例子,比如我们js中的addEventListenerjQuery中的on,这两个相比我们都很熟悉,都是为某个dom元素进行绑定一个事件,比如我们为一个按钮绑定了click事件,那么当这个按钮触发了click事件之后,那么我们绑定时候定义的函数也会被执行,

Events

大多数 Node.js 核心 API 都采用惯用的异步事件驱动架构,其中某些类型的对象(触发器)会周期性地触发命名事件来调用函数对象(监听器)。

node中有一个核心模块就是events,他实现的功能其实就是我们的发布订阅模式,上面的解释其实就是说明了events这个模块在我们node的一些核心api中有些十分重要的作用,比如说我们的tcphttp,流这些都用到了我们的events模块。

我们就来看看events模块中的那些核心方法和属性:

首先是第一块,这一块主要是说明了node中的一个对于新增事件或者移除事件时候要先触发这对应的事件

  • newListener事件,node中定一个这样一个规范,就是说当我们订阅任何新事件的时候都会触发一个newListener事件,传入的参数就是订阅的新事件名
  • removeListener事件,同上,当我们移除一个事件的事件就会触发removeListener事件

这一部分是介绍事件的监听器数量相关的函数

  • EventEmitter.defaultMaxListeners,规定了每个事件的监听器(也就是回调函数)的最大数量,默认是10,当超过这个数量的时候会报一个警告
  • emitter.setMaxListeners(n) 设置最大监听数
  • emitter.getMaxListeners 返回 EventEmitter 当前的最大监听器限制值

这一部分主要是对于注册的事件或者某个已注册的事件的监听数数组做的操作

  • emitter.eventNames 返回一个列出触发器已注册监听器的事件的数组
  • emitter.listenerCount(eventName) 返回正在监听名为 eventName 的事件的监听器的数量
  • emitter.listeners(eventName) 返回名为 eventName 的事件的监听器数组的副本

这部分是events的核心方法,on绑定监听,once绑定的事件只触发一次,emit用来发射事件

  • emitter.addListener 也就是on的别名,就是订阅某一个事件比如 my.on('buy',()=>{console.log('没钱了')})
  • emitter.on(eventName, listener) 添加 listener 函数到名为 eventName 的事件的监听器数组的末尾。 不会检查 listener 是否已被添加。 多次调用并传入相同的 eventNamelistener 会导致 listener 被添加与调用多次。
  • emitter.once(eventName, listener) 添加一个单次 listener 函数到名为 eventName 的事件。 下次触发 eventName 事件时,监听器会被移除,然后调用
  • emitter.prependListener(eventName, listener) 和on方法类似,但是调用这个方法会吧监听器添加到数组的首项
  • emitter.prependOnceListener(eventName, listener) 和once类似,也依然是讲监听器添加到数组首项
  • emitter.emit 发射事件,会按照注册事件时候放置的回调函数的顺序进行执行

这一部分是做事件移除

  • emitter.removeAllListeners([eventName]) 移除全部或指定 eventName 的监听器
  • emitter.removeListener(eventName, listener)从名为 eventName 的事件的监听器数组中移除指定的 listener
  • emitter.off(eventName, listener) 就是removeListener的别名

My-EventEmitter

ok,老套路,既然我们都知道了这些方法是什么意思 那么我们就跟着来手写一个EventEmitter!

function EventEmitter(){
    this._events = {} ;// 事件库
    this.count = 0 ; // 同类事件最大的监听数
}
// 最大监听数默认是10
EventEmitter.defaultMaxListeners = 10;
// 获取最大监听数
EventEmitter.prototype.getMaxListeners = function(){
    return this.count || EventEmitter.defaultMaxListeners
}
// 设置最大监听数
EventEmitter.prototype.setMaxListeners = function(n){
    this.count = n;
    return this
}
// addListener 和  on同样的作用
EventEmitter.prototype.addListener = EventEmitter.prototype.on;

/**
 * 实现on方法
 * @param {string} eventName 订阅事件名
 * @param {function} callback 回调函数
 * @param {boolean} flag 是否添加到函数列表的第一位(是否第一次执行)
 */
EventEmitter.prototype.on = function(eventName,callback,flag){
    // 通常情况下 我们使用EventEmitter是通过util.inherits来继承EventEmitter的公有方法,而EventEmitter的_events属性是在实例上的 因此 如果检测到是通过继承拿到的 那么就为这个类添加_events属性
    if(!this._events) this._events = Object.create(null);
    // 如果事件名不是newListener 那么检测如果订阅了newListener那么就执行
    if(eventName !== 'newListener' && this._events["newListener"] && this._events["newListener"].length){
        this._events['newListener'].forEach(fn =>fn(eventName));
    }
    if(this._events[eventName]){
        // 如果之前添加过这个事件
        if(flag){
            this._events[eventName].unshift(callback)
        }else{
            this._events[eventName].push(callback)
        }
    }else{
        this._events[eventName] = [callback]
    }
     // 判断订阅事件数 超过最大个数 打印警告
     if (this._events[eventName].length >= this.getMaxListeners()) {
        console.warn('MaxListenersExceededWarning');
    }
}
/**
 * 返回当前订阅的事件名集合
 */
EventEmitter.prototype.eventNames = function(){
    return Object.keys(this._events)
}
/**
 * 返回订阅的事件绑定的函数个数
 * @param {string} eventName 
 */
EventEmitter.prototype.listenerCount = function(eventName){
    return (this._events[eventName] || []).length
}
/**
 * 返回订阅的事件绑定函数的copy
 * @param {string} eventName 
 */
EventEmitter.prototype.listeners = function(eventName){
    return [...(this._events[eventName] || [])]
}

EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
/**
 * 移除某个事件下的执行函数
 * @param {string} eventName 
 * @param {function} callback 
 */
EventEmitter.prototype.removeListener = function(eventName,callback){
    if(this._events[eventName]){
        this._events[eventName] = this._events[eventName].filter((fn) => {
            return fn != callback && fn.realCallback !== callback;// 这里判断了fn是不是等于callback,同时也解决我们因为once操作而把原有的回调函数包裹一层的问题,通过暴露出来的原有回调作对比
        })
    }
    return this
}
/**
 * 移除某个事件的所有回调
 * @param {string} eventName 
 */
EventEmitter.prototype.removeAllListeners = function(eventName){
    if(this._events[eventName]){
        this._events[eventName] = []
    }
    return this
}
/**
 * 添加一个单次 listener 函数到名为 eventName 的事件。 下次触发 eventName 事件时,监听器会被移除,然后调用。
 * @param {string} eventName 
 * @param {function} callback 
 * @param {boolean} flag 
 */
EventEmitter.prototype.once = function(eventName,callback,flag){
    function wrap(){
        callback();
        this.removeListener(eventName,wrap)
    }
    wrap.realCallback = callback;// 这里因为once的回调函数被重新包裹了一层 因此当你removeListener的时候如果用原来的fn去判断是否相等的话就判断不到了,因此需要把原来的fn暴露到一个属性上,方便删除操作的时候去做对比
    this.on(eventName,wrap,flag)
    return this;
}
/**
 * 向数组前面添加事件
 * @param {string} eventName 
 * @param {function} callback 
 */
EventEmitter.prototype.prependListener = function (eventName, callback) {
    this.on(eventName, callback, true);
}
/**
 * 向数组前面添加事件 并且只执行一次
 * @param {string} eventName 
 * @param {function} callback 
 */
EventEmitter.prototype.prependOnceListener = function(eventName, callback){
    this.once(eventName,callback,true);
}
/**
 * 触发事件
 * @param {string} eventName 
 */
EventEmitter.prototype.emit = function (eventName) {
    if (this._events[eventName]) {
        this._events[eventName].forEach(fn => {
            fn.call(this);
        });
    }
}

跟着思路来了一遍,是不是发现突然开窍,大喊一声,学会了!快删!