Node.js 是单进程单线程应用程序,但是因为 V8 引擎提供的异步执行回调接口,通过这些接口可以处理大量的并发。 Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数。
一.事件驱动编程
Node.js 使用事件驱动模型,当Node启动其服务器,它可以简单地启动它的变量,声明的函数,然后简单地等待发生的事件。当web server接收到请求,就把它关闭然后进行处理,接着去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作,这也被称之为非阻塞式IO或者事件驱动IO。在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。处理模型如下图。整个事件驱动的流程非常简洁,有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。
Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下代码:
// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();
以下程序绑定事件处理程序:
// 绑定事件及事件的处理程序eventEmitter.on('eventName', eventHandler);
可以通过程序触发事件:
// 触发事件eventEmitter.emit('eventName');
二.事件处理流程
创建 main.js 文件,代码如下所示:
// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();// 创建事件处理程序var connectHandler = function connected() { console.log('连接成功。'); // 触发 data_received 事件 eventEmitter.emit('data_received');}// 绑定 connection 事件处理程序eventEmitter.on('connection', connectHandler); // 使用匿名函数绑定 data_received 事件eventEmitter.on('data_received', function(){ console.log('数据接收成功。');});// 触发 connection 事件 eventEmitter.emit('connection');console.log("程序执行完毕。");
接下来让我们执行以上代码:
$ node main.js连接成功。数据接收成功。程序执行完毕。
三.应用程序处理
对于事件环机制的一个恰当的比喻是将它比作一个邮递员, 而每个事件就好比是邮递员需要送达的一封邮件。 他手上有大量需要依序送达的邮件, 而他需要按照指定路线来送达这些邮件, 而回调函数就好比这些路线, 由于邮递员只有一双腿, 所以他每次只能按照指定路线来送达一封邮件, 也就是说, 他每次只能处理一个回调函数。 在他按照某条指定路线送达某封邮件的途中, 可能会有人给他新的邮件, 这就是代码中要求他处理的新的事件。 这种情况下, 邮递员将会转而处理新的事件(包括触发事件、 初始化该事件的回调函数等), 在该事件处理完毕之后, 转而送达原本要送达的邮件, 也就是说, 在回调函数的执行过程中,他将转而处理新的事件, 在该事件处理完毕之后, 转而继续处理原回调函数。
让我们用实际应用程序中的例子来看一下该邮递员的行为。
假设我们有一个HTTP服务器, 它需要接收客户端请求, 根据请求参数从数据库中获取一些数据, 然后将这些数据返回给用户。 在这种场景下我们有几个事件需要处理。 首先用户在页面上向服务器端发出一个客户端请求, 这将触发HTTP服务器对象的一个request事件, 在事件回调函数(假定函数被命名为callbackA)中处理该请求, 根据请求参数来决定需要从数据库中获取哪些数据,然后向数据库中发出获取数据的请求,并且将另一个函数(假定函数被命名为callbackB)指 定为当数据库获取到数据时触发的response事件的事件回调函数。 当向数据库发出获取数据的请求后, 就可以继续执行callbackA回调函数中的后续代码。 当数据库中数据获取完毕后,将触发数据库对象的response事件,调用callbackB回调函数将数据返回给用户。
在这个处理中, 由于Node.js中采用的是非阻塞1/0机制, 因此不需要等待数据库中的数据获取完毕才能继续执行callbackA回调函数中的后续代码, 而是为数据库对象绑定一个 新的事件并初始化该事件的事件回调函数。