Node模块之事件(events)详解

Node中的事件模型就是我们常见的订阅发布模式,Nodejs核心API都采用异步事件驱动,所有可能触发事件的对象都是一个继承自EventEmitter类的子类实例对象。简单来说就是Node帮我们实现了一个订阅发布模式。

1.订阅发布模式(Subscribe/Publish)

订阅发布模式定义了一种一对多的依赖关系,在Node中EventEmitter 对象上开放了一个可以用于监听的on(eventName,callback)函数,允许将一个或多个函数绑定到对应的事件上。当 EventEmitter 对象触发一个事件时,所有绑定在该事件上的函数都被同步地调用!这种模式在node中大量被使用,例如:后续文章中我们会说到的流等,那现在我们就来一步步实现Node中的events模块!

2.实现events模块

我先举个我最喜欢举的例子:男人梦想着有钱,有钱可以买包、买车。当然有一天有了钱就要让这些梦想一一实现。

2.1 on和emit的实现

on的作用是对指定事件绑定事件处理函数,emit则是将指定的事件对应的处理函数依次执行

const EventEmitter = require('events');
class Man extends EventEmitter { }
const man = new Man();
let buyPack = () => {
    console.log('买包');
}
let buyCar = () => {
    console.log('买车');
}
man.on('有钱了', buyPack);
man.on('有钱了', buyCar);
man.emit('有钱了'); // 买包 、 买车
复制代码

对此我们来自己实现events对应的方法!

function EventEmitter(){
    EventEmitter.init.call(this); // 初始化内部私有方法
}
EventEmitter.init = function(){
    // 为了存放一对多的对应关系 例如后期 
    // {'有钱',[buyPack,buyCar],'没钱':[hungry]}
    this._events = Object.create(Object.create(null));
}
EventEmitter.prototype.on = function(eventName,callback){ // 绑定事件
    // 调用on方法就是维护内部的_events变量,使其生成一对多的关系
    if(this._events[eventName]){ // 如果存在这样一个关系只需在增加一项即可
        this._events[eventName].push(callback)
    }else{
        // 增加关系
        this._events[eventName] = [callback]
    }
}
EventEmitter.prototype.emit = function(eventName){ // 触发事件
    if(this._events[eventName]){
        // 如果有对应关系
        this._events[eventName].forEach(callback => {
            callback();
        });
    }
}
// 导出事件触发器类
module.exports = EventEmitter; 
复制代码

我们多次调用emit会将事件对应的函数多次执行。假如说在没有调用之前我后悔了,不想买车了。此时我们还要提供一个取消绑定的方法。

2.2 removeListener

 man.on('有钱了', buyPack);
 man.on('有钱了', buyCar);
+man.removeListener('有钱了',buyCar)
 man.emit('有钱了');  // 买包

// events
+EventEmitter.prototype.removeListener = function(eventName,callback){
+    if(this._events[eventName]){ // 如果绑定过,我在尝试着去删除
+        // filter返回false就将当前项从数组中删除,并且返回一个新数组
+        this._events[eventName] = this._events[eventName].filter(fn=>fn!==callback);
+    }
+}
复制代码

这样我们就实现了events中比较核心的三个方法on、emit、removeListener,在此同时我们希望在emit的时候可以传递参数,参数会传入执行的回调函数中。

-let buyPack = () => {
-    console.log('买包');
+let buyPack = (who) => {
+    console.log(who+'买包');
 }
-let buyCar = () => {
-    console.log('买车');
+let buyCar = (who) => {
+    console.log(who+'买车');
 }
 man.on('有钱了', buyPack);
 man.on('有钱了', buyCar);
 man.removeListener('有钱了',buyCar);
-man.emit('有钱了'); 
+man.emit('有钱了','给心仪的女孩'); 
复制代码
-EventEmitter.prototype.emit = function(eventName){ // 触发事件
+// 此时emit时可能会传递多个参数,除了第一个外均为回调函数触发时需要传递的参数
+EventEmitter.prototype.emit = function(eventName,...args){ // 触发事件
     if(this._events[eventName]){
         // 如果有对应关系
         this._events[eventName].forEach(callback => {
-            callback();
+            callback.apply(this,args); // 在执行回调时将参数传入,保证this依然是当前实例
         });
     }
 }
复制代码

剩下的内容就是基于这些代码进行扩展

2.3 扩展once方法

我们希望买包的事件多次触发emit只执行一次,也就代表执行一次后需要将事件从对应关系中移除掉。

-man.on('有钱了', buyPack);
+man.once('有钱了', buyPack); // 只绑定一次
 man.on('有钱了', buyCar);
 man.removeListener('有钱了',buyCar);
+man.emit('有钱了','给心仪的女孩'); // 此时代码执行后,对应的buyPack会被移除掉
+man.emit('有钱了','给心仪的女孩'); // buyPack动作将不会再次执行 

// events
+EventEmitter.prototype.once = function(eventName,callback){
+    function wrap(...args){ // wrap执行时会传入参数
+        callback.apply(this,args); // 将once绑定的函数执行
+        // 当wrap触发后移除wrap
+        this.removeListener(eventName,wrap);
+    }
+    wrap.listener = callback; // 这里要注意此时绑定的是wrap,防止删除时无法删除,增加自定义属性
+    this.on(eventName,wrap); // 这里增加了warp函数,目的是为了方便移除
+    
+}
 EventEmitter.prototype.removeListener = function(eventName,callback){
     if(this._events[eventName]){ // 如果绑定过,我在尝试着去删除
         // filter返回false就将当前项从数组中删除,并且返回一个新数组
-        this._events[eventName] = this._events[eventName].filter(fn=>fn!==callback);
+        // 如果函数上的自定义属性和我们要删除的函数相等也将将这个函数删除
+        this._events[eventName] = this._events[eventName].filter(fn=>fn!==callback&&fn.listener!==callback);
     }
 }
复制代码

2.4 newListener方法

EventEmitter 实例会在一个监听器被添加到其内部监听器数组之前触发自身的 'newListener' 事件。

+man.on('newListener',function(eventName,callback){
+    console.log(eventName); //触发两次有钱了
+})
 man.once('有钱了', buyPack); // 只绑定一次
 man.on('有钱了', buyCar);

// events
  EventEmitter.prototype.on = function(eventName,callback){ // 绑定事件
+    if(eventName !== 'newListener'){ // 如果监听的是newListener
+        // 用户如果监听了newListener事件,我们还要触发newListener事件执行
+        this._events.newListener&&this._events.newListener.forEach(fn=>fn(eventName,callback))
+    }
复制代码

2.5 监听数量控制

每个事件默认可以注册最多 10 个监听器。 当然我们也可以控制监听个数,此规定并不是一个硬性限制。 EventEmitter 实例允许添加更多的监听器,但会向 stderr 输出跟踪警告,表明可能会导致内存泄漏。

+console.log(EventEmitter.defaultMaxListeners); // 默认允许监听数量为10超过10会出现警告
+man.setMaxListeners(1) // 设置最大监听数
+console.log(man.getMaxListeners()); // 获取监听数
 man.on('newListener',function(eventName,callback){
    console.log(eventName,callback);
 });
 man.once('有钱了', buyPack); // 只绑定一次
 man.on('有钱了', buyCar);
+man.on('有钱了', buyCar);
+console.log(man.listenerCount('有钱了'));// 监听个数3
+//MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 
+//2 有钱了 listeners added. Use emitter.setMaxListeners() to increase limit
 man.removeListener('有钱了',buyPack);
 man.emit('有钱了','给心仪的女孩'); // 此时代码执行后,对应的buyPack会被移除掉
 man.emit('有钱了','给心仪的女孩'); // buyPack动作将不会再次执行


 // events
 EventEmitter.init = function(){
    // 为了存放一对多的对应关系 例如后期 
    // {'有钱',[buyPack,buyCar],'没钱':[hungry]}
    this._events = Object.create(Object.create(null));
+   this._maxListeners = undefined; // 默认实例上没有最大监听数
 }
 ------------------------------
+// 默认监听数量是10
+EventEmitter.defaultMaxListeners = 10
+EventEmitter.prototype.setMaxListeners = function(count){
+    this._maxListeners = count;
+}
+EventEmitter.prototype.getMaxListeners = function(){
+    if(!this._maxListeners){ // 如果没设置过那就是10个
+        return EventEmitter.defaultMaxListeners;
+    }
+    return this._maxListeners
+}
--------------------------------
 EventEmitter.prototype.on = function(eventName,callback){ // 绑定事件
     // .........
+    //如果添加的数量和最大监听数一致抛出警告
+    if(this._events[eventName].length === this.getMaxListeners()){
+        console.warn('Possible EventEmitter memory leak detected. ' +
+        `${this._events[eventName].length} ${String(eventName)} listeners ` +
+        'added. Use emitter.setMaxListeners() to ' +
+        'increase limit')
+    }
 }
 ------------------------------
+EventEmitter.prototype.listenerCount =  function(eventName){
+    return this._events[eventName].length
+}
复制代码

我们处理了一下对于事件监听的个数

2.6 eventNames函数

列出触发器已注册监听器的事件的数组

const EventEmitter = require('./events');
class Man extends EventEmitter { }
let man = new Man();
man.on('有钱',()=>{console.log('买车')});
man.on('没钱',()=>{console.log('饿肚子')});
console.log(man.eventNames()); // 有钱 没钱

// events
EventEmitter.prototype.eventNames = function(){
    return Object.keys(this._events); // 将对象转化成数组
}
复制代码

2.7 removeAllListeners

移除全部或指定 eventName 的监听器。

const EventEmitter = require('./events');
class Man extends EventEmitter { }
let man = new Man();
man.on('有钱',()=>{console.log('买车')});
man.on('没钱',()=>{console.log('饿肚子')});
man.removeAllListeners()
console.log(man.eventNames()); // []

// events
EventEmitter.prototype.removeAllListeners = function(eventName){
    if(type){
        delete this._events[eventName];
    }else{
        this._events = Object.create(null);
    }
}
复制代码

2.8 prependListener

添加 listener 函数到名为 eventName 的事件的监听器数组的开头。

const EventEmitter = require('./events');
class Man extends EventEmitter { }
let man = new Man();
man.on('有钱',()=>{console.log('买房')});
man.prependListener('有钱',()=>{console.log('买车')}); // 在事件监听器数组开头追加
man.emit('有钱'); // 买车 买房

// events
// bool代表是正序还是倒序插入数组中
-EventEmitter.prototype.on = function(eventName,callback){ // 绑定事件
+EventEmitter.prototype.on = function(eventName,callback,bool){ // 绑定事件
     if(eventName !== 'newListener'){ // 如果监听的是newListener
         // 用户如果监听了newListener事件,我们还要触发newListener事件执行
         this._events.newListener&&this._events.newListener.forEach(fn=>fn(eventName,callback))
     }
     // 调用on方法就是维护内部的_events变量,使其生成一对多的关系
     if(this._events[eventName]){ // 如果存在这样一个关系只需在增加一项即可
-        this._events[eventName].push(callback);
+        if(bool){
+            this._events[eventName].unshift(callback);
+        }else{
+            this._events[eventName].push(callback);
+        }
     }

EventEmitter.prototype.prependListener = function(eventName,callback){
    this.on(eventName,callback,true);// 仍然调用on方法只是多传递一个参数
}
复制代码

2.9 prependOnceListener

添加一个单次 listener 函数到名为 eventName 的事件的监听器数组的开头。

const EventEmitter = require('events');
class Man extends EventEmitter { }
let man = new Man();
man.on('有钱',()=>{console.log('买房')});
man.prependOnceListener('有钱',()=>{console.log('买车')}); // 在事件监听器数组开头追加
man.emit('有钱'); // 买车 买房
man.emit('有钱'); // 买房

// events
EventEmitter.prototype.prependOnceListener = function(eventName,callback){
    function wrap(...args){ // wrap执行时会传入参数
        callback.apply(this,args); // 将once绑定的函数执行
        // 当wrap触发后移除wrap
        this.removeListener(eventName,wrap);
    }
    wrap.listener = callback; // 这里要注意此时绑定的是wrap,防止删除时无法删除,增加自定义属性
    this.on(eventName,wrap,true); // 这里增加了warp函数,目的是为了方便移除
}
// 这里的wrap方法可以进一步封装,这里就不做演示了。
复制代码

到此我们就将node中整个events库从头到尾完善的写了一遍。如果上述代码需要链式调用需要我们返回this来实现

喜欢的点个赞吧^_^! 支持我的可以给我打赏哈!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值