nodejs中的events模块和EventEmitter对象
事件是nodejs中的一个核心概念,node所声称的非阻塞I/O就是通过事件驱动的模式实现的,而node使用js语言来编写程序,js本身就具有事件驱动的特性。在node中,基本上很多工作都是通过事件驱动完成的,处理用户请求、读写文件、从数据库获取数据等都是通过注册和触发事件完成的。
我们先看看node官网上提供的那个经典的创建http服务器处理客户请求的示例:
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/');
这段代码先加载http
核心模块然后调用http
的createServer
方法创建一个http服务器,这个方法的参数就是用于处理客户端请求的函数,这段代码实际上监听了request
事件,而所传入的函数就是事件处理函数,当有请求到达的时候就会触发这个事件,进而调用这个处理函数。这段代码还可以这么写。
var http = require('http'); var server = http.createServer(); server.on('request',function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }); server.listen(1337,'127.0.0.1');
这样一眼就可以看出我们监听了request
这个事件。这个事件是http
这个核心模块所提供的事件,node的核心模块还提供了很多类似的事件,但这些都是已经提供好的,我们怎么使用node的事件机制来编写自己的事件呢?
先看一个普通的示例:
var events = require('events'); var eventEmitter = new events.EventEmitter(); var ringBell = function ringBell() { console.log('ring ring ring'); } eventEmitter.on('doorOpen', ringBell); eventEmitter.emit('doorOpen');
运行这段代码会在控制台输出ring ring ring
,这段代码先加载了node的events
模块,这也是node提供的核心模块,然后创建了一个EventEmitter
的实例,这个类就是用于处理事件注册和监听工作的。在这个示例里我们调用EventEmitter
对象的on
方法,将ringBell
这个函数作为事件处理函数注册到doorOpen
这个事件上,然后再调用EventEmitter
对象的emit
方法触发这个事件,这样ringBell
方法就会被调用。
整个事件驱动的流程就是这么实现的,非常简洁。有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。一个事件上可以注册多个处理函数,就以doorOpen
这个事件为例,我们可以使用EventEmitter.on
注册多个处理函数:
eventEmitter.on('doorOpen', ringBell); eventEmitter.on('doorOpen', doSomething); eventEmitter.on('doorOpen', doSomethingElse); eventEmitter.emit('doorOpen');
最后当调用EventEmitter.emit
触发这个事件后所有的事件处理函数都会执行。node默认一个事件最多可以注册10个处理函数,这个值可以修改。我们也可以给事件处理函数传递参数,假设我们把门打开后的声音可能会不断变化,并且这个声音要在开门的时候传递:
eventEmitter.on(‘doorOpen’, function(ring) { Console.log(ring); }); eventEmitter.emit(‘doorOpen’, ‘ringeling’);
doorOpen
这个事件的处理函数接收一个参数,这个参数就是在emit
方法中传入的,所有需要传入事件处理函数的参数都在emit
的第一个参数后面传入。
通过上面的示例我们基本上了解了node中的事件模型是怎么使用的,但这个时候会有一个疑问,我们可以看到上面http服务器的示例中直接调用createServer
这个方法就注册了request
这个事件,所以我们的门能不能提供一个方法注册铃声响起这个事件,我们将这个事件命名为ringing
,并且有一个open
的方法,在门被打开的时候调用这个方法,然后触发ringing
这个事件呢?
答案是可以:
//door.js var events = require('events'); var util = require('util'); function Door(colour) { this.colour = colour; events.EventEmitter.call(this); this.open = function(sound){ this.emit('ringing,sound); }; this.addRing = function(handleRinging){ this.on('ringing,handleRinging); }; } util.inherits(Door, events.EventEmitter); module.exports = Door; //use-door.js var Door = require('./door'); var frontDoor = new Door('brown'); frontDoor.addRing(function(sound){ console.log(sound); }); frontDoor.open('ringing...');
我们首先创建了一个door
的模块,这个模块会提供一个Door
的类,这个类继承了events.EventEmiiter
:
function Door(colour) { this.colour = colour; events.EventEmitter.call(this); } util.inherits(Door, events.EventEmitter);
Door
构造函数中的events.EventEmitter.call(this)
是调用EventEmitter
的构造方法,util.inherits(Door, events.EventEmitter)
将EventEmiiter
的属性和方法拷贝到Door
对象中,这样我们就可以直接在Door
方法中调用this.on
和this.emit
这两个方法了。接下来我们Door
提供了两个方法,addRing
用于给门安装铃铛,open
方法用于开门。接下来我们在use-door.js
中创建一个Door
对象,然后调用addRing
安装门铃,调用这个方法需要传入一个函数,这个函数就是用于处理门铃事件的,然后调用open
方法开门。这样我就实现了一个门的类,这个类中有一个ringing
的事件,这个事件会在安装门铃的时候注册,并且会在开门的时候触发。
关于EventEmiiter
中的一些跟事件相关的方法和属性请参加具体文档,关于使用node中的events
模块和EventEmitter
这个对象的内容就介绍到这里,希望对你有帮助,改天有时间我们再介绍一下EventEmitter
的其他一些特性。