Node模块之事件(events)详解

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

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

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

2.实现events模块

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

2.1 on和emit的实现

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

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的时候能够传递参数,参数会传入执行的回调函数中。ui

-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依然是当前实例
         });
     }
 }
复制代码

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

2.3 扩展once方法

咱们但愿买包的事件屡次触发emit只执行一次,也就表明执行一次后须要将事件从对应关系中移除掉。spa

-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' 事件。prototype

+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来实现

喜欢的点个赞吧^_^! 支持个人能够给我打赏哈!

dashang
相关文章
相关标签/搜索