• 首页
  • nodejs
  • node.js 如何继承 events 自定义事件及触发函数

node.js 如何继承 events 自定义事件及触发函数

events 是node.js的核心api ,几乎大部分node.js 的api都继承 events 类(javascript中没有类,也不存在继承,确切说是模拟类和继承,点击查看)

比如我们常见的 http , stream ,fs .....等等.

因为node.js的实现就是异步非阻塞io,通过事件轮询触发异步回调的机制,在单线程运行模式下,合理应用服务器资源才是制胜法宝,正是因为事件轮询才使得异步IO在高并发处理下游刃有余,所以大部分的包去继承 events 似乎合情合理.


下面我们可以看一个常见的事件处理,但是我们用的时候似乎并没有当回事:

var req = http.request(params, function(res) {
    res.on("data", function(data) {
        console.log("response data", data);
    });
    res.on("end", function() {
         console.log("response end");
    });
});
req.end();

上面是一个标准的异步模式,而作为单线程的node.js绝对不会选择等待,那这个直接在node.js是如何处理的. 

node.js运行在单线程模式下,但是可以有多个进程来处理,我们假设A进程执行api操作,B进程负责事件轮询.那么A调用上面的代码会马上得到一个结果,结果内容就是:"你的请求已经受理了" 然后A进程接着处理后面排队的其他api.


这里来一个小插曲:

"如果是其他同步语言,会这样处理,执行到上面的代码以后会得得到什么结果? 什么也没有,只能一直在这里站着等待结果....后面的api也全部排队等待中ing.....突然A大声喊道"有结果了",这一声差点把睡着的api吓尿,然后它拿起结果屁颠屁颠跑了,接着下一个api来请求.....

所以同步语言该如何解决这种资源浪费? 多开几个窗口,大家分散在不同窗口排队(这就是多线程) 然后各个线程要互相通报状态保持数据同步等....而node.js就避免了这种顾虑"


我们从插曲回来

上面的api调用后 A马上告知它结果,但是这个结果并不是它想要的.

于是B进程该上场表现了,它手里拿着一大串事件处理函数的引用.

当它发现了 "data" 事件,马上把 "data" 事件对应的处理函数引用给了"data"事件,

同理当它遇到 "end" 事件,马上把 "end"事件处理函数塞给了"end" ,

当某个事件执行完返回数据时马上交给B,而B再大声通知刚才调用的api,"你的业务逻辑数据返回来了",

这个可能刚才那个请求api已经吃了个盖饭,正在悠然的抽着烟,然后很娴熟的掐掉烟头,跑回去拿到了自己业务逻辑所需要的数据.


从上面描述可以看到,api在调用过程中一共拿到了2个结果:

第一个就是把代码提交进程A的时候,A马上给它返回一个结果"你的请求已经受理",虽然这个结果对业务没啥作用,但是对于api是有用的,它得知这个通知后,就不需要等待了.自己出去吃饭抽烟该干啥干啥去.

第二个得到的结果才是真正数据处理完以后的结果,这部分数据可能业务逻辑非常需要.


其中拿到第二个结果的过程中,B进程手提事件绑定函数,四处奔波寻找事件.

这是一个什么过程,就是当事件被触发时,就马上执行事件绑定函数.所以前提是:首先有事件绑定函数,其次注册了事件,(所以上面会提到B进程手里拿着事件绑定函数坐等事件)


看来要使用事件,首先我们需要有事件绑定函数,其次需要注册事件,而在node.js中 events 核心库正好实现了这几个api 

events.EventEmitter 类提供了如下api

1.事件绑定函数 

emitter.on(event, function(){
    //业务处理
});

还有另外一种绑定函数的方法,很不常用,和上面的等价

emitter.addListener(event,function(){
    //业务处理
});

2.绑定一次性函数,和上面的一样,给某个事件绑定一个函数,不同的是,对此事件只监听一次,也就是说,这个绑定函数只运行一次.

emitter.once(event,function(){
    //业务处理
});

3.移除一个事件绑定函数

emitter.removeListener(event,function(){
    //移除指定事件
});

4.移除所有绑定事件函数,注意参数是数组,数组元素是事件名称

emitter.removeAllListener([event,...]);

5.设置事件绑定函数上限,

node.js 建议我们在某个事件上的绑定函数不要超过10个,如果达到这个上限会予以警告,如何消除这个警告?用下面的api

emitter.setMaxListeners(n); //这样一个事件最多可以绑定n个函数

6.事件发射器,注册事件

emitter.emit(event,[arg1],[arg2])

用这个api注册事件,也就是自定义事件.


剩下的 几个api不一一列举了.


如何在一个自定义的类里使用事件? 答案是: 继承!

让你定义的类继承 events ,然后就可以使用上面的api了.


来一个实际的示例.一个SNS社区,有博客,问答,小组等模块,每个模块都有用户数据入口,比如 用户写博客要保存, 提问一个问题也要保存,在小组发表一个话题也要保存.

期间,不论我保存博客,问题还是小组话题,我都需要把数据写入数据库,统计个板块内容数量,统计用户发内容的数量.

如果我创建博客类,问答类,小组话题类,然后每个类再实现上面功能,这是一个重复且耗时的过程.这个应用场景就非常适合事件,重复且调用频繁


简单的框架:

eventTest 是这个示例的项目文件夹

    methord 为逻辑应用层(或者为方法层)  //处理业务逻辑 ,调用 模型层实体对象的方法

    model     为模型层(里面有一个父类 _base.js 和若干个子类,暂时只有一个blogInfo.js 博客类)  //个性化定义模型 属性,方法,事件

    app.js     启动文件 //启动服务器,解析路由,调用方法层api (不做任何业务逻辑处理, 调用 --> 输出给用户 )


愿景:

有没有一种可能,我创建一个公用基础类_base,此类有一个工厂方法onEvent, onEvent专门用来绑定事件函数.

还有另外一个工厂方法emitEvent, emitEvent 专门用来注册发射事件.


代码实现:

var events=require('events');
var util=require('util');

function _base(){
	this.emitter=new events.EventEmitter(this);
};

util.inherits(_base,events.EventEmitter); //继承

_base.prototype.onEvent=function(eventName,callback){
	this.emitter.on(eventName,callback);
}

_base.prototype.emitEvent=function(eventName,arg){
	this.emitter.emit(eventName,arg);
}


module.exports=_base;

注意: node.js 中实现继承要使用工具类 util 中的 inherits 方法,下一篇博文专门用来讲此继承方式


model 实体层:

博客类 blogInfo   --> 继承自上面 _base 基础类

小组类 groupInfo  --> 继承自上面 _base 基础类

问答类 askInfo  --> 继承自上面 _base 基础类


我们只用 blogInfo 来举例说明,其他2个类大同小异:

因为blogInfo 继承了 _base 类,所以它也有了2个工厂方法,因为针对不同的类,可能需要发射不同的事件,定义不同的事件监听函数.

我们想实现在博客保存的时候, 内容提交到数据库,统计板块数量,统计用户内容数量,保存结束. 

所以我们把个性化事件及事件绑定函数放在此类里定义:

var _base=require('./_base');
var util=require('util');
function blogInfo(){
    this.base=new _base();
}

util.inherits(blogInfo,_base);

blogInfo.prototype.onSave=function(blog){
	this.base.onEvent('saveStart',function(blog){
		console.log('saveStart',blog);
	});

	this.base.onEvent('blogCount',function(blog){
		console.log('blogCount',blog);
	});

	this.base.onEvent('userInfoCount',function(blog){
		console.log('count',blog);
	});

	this.base.onEvent('saveEnd',function(blog){
		console.log('saveEnd',blog);
	});
};

blogInfo.prototype.emitEvent=function(blog){

	this.base.emitEvent('saveStart',blog);

	this.base.emitEvent('blogCount',blog);

	this.base.emitEvent('userInfoCount',blog);

	this.base.emitEvent('saveEnd',blog);
};

上面的blogInfo 继承了 _base ,然后在原型对象上定义方法onSave , onSave 方法里调用父类的 onEvent 工厂方法分别给4个事件, saveStart ,blogCount, userInfoCount , saveEnd  定义了对应事件的绑定函数.

接着blogInfo 在原型对象上定义了方法 emitEvent 方法,并在方法里发射了4个事件 saveStart ,blogCount, userInfoCount , saveEnd


其他模型类似blogInfo的定义,(小组话题,问答等,有兴趣自己实现)


方法层定义一个blog的业务处理逻辑块:

var BlogInfo=require('../model/blogInfo');


exports.blog_save=function(newblog){

	var blogInfo=new BlogInfo();

	blogInfo.onSave(newblog);

	blogInfo.emitEvent(newblog);
};

很简单的功能,就是创建博客实体对象,把新博客数据调用 onSave 方法让4个事件先监听好(4个观察者,坐等事件发生)

然后再调用 事件发射器把事件注册,(注意,一定要先又事件监听函数,然后出发事件,才有效果,否则事件发射结束,你无路如何也执行不了事件绑定函数,所以上面代码调用顺序绝对不能反)


app.js  代码启动文件

var http = require('http');        
var blog= require('./methord/blog');


var webServer = function (req, res){ 
	if(req.url!='/favicon.ico'){
		var newblog={title:"标题",content:"内容"};
		blog.blog_save(newblog);

		res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'}); 
		res.write('<html><body>'); 
		res.write('<h1>*** save blog ***</h1>'); 
		res.write('<h2>Hello!</h2>'); 
		res.end('</body></html>'); 
	}
};

var createWebServer=http.createServer(webServer);

createWebServer.listen(8000);
console.log('listen 8000');

一个不能再简单的node.js 服务器端实现,没有路由匹配.....此demo只在实现事件发射及相关绑定函数.

因为node.js默认一个request 总是会请求2次 why? 不明白的点链接查看.所以我在路由里简单加了个 if 判断.

不论什么请求,我们都能模拟一个 保存blog的情形.因为保存我们绑定了4个监听函数,然后触发保存方法的一刹那,我们会发射4个事件,这是事件观察者(监听函数)会以迅雷不及掩耳之势去捕捉,然后调用绑定函数帮我们实现了4个愿景..... 看下输出的结果吧:

页面输出结果,精美UI亮瞎你的双眼!!!

控制台捕获事件的结果怎么样? 

4个函数监听器各负其职,监听到了事件,而且成功执行了事件绑定函数.


*****************END*****************



回到顶部