通常js给我们的第一映像就是运行在客户端浏览器上面的脚本,通过node.js我们可以在服务端运行javascript.
node.js是基于单线程无阻塞异步式的I/O,异步式的I/O指的是当遇到I/O操作的时候,线程不阻塞而是进行下面的操作,那么I/O操作完成之后,线程时如何知道该操作完成的呢?
当操作完成耗时的I/O操作之后,会以事件的形式通知I/O操作的线程完成,线程会在特定的时候来处理这个事件,进行下一步的操作,为了完成异步I/O,线程必须有事件循环的机制,不停的坚持是否有没有完成的事件,依次完成这些事件的处理。
而对于阻塞式I/O,线程遇到耗时的I/O操作会停止继续执行,等待操作的完成,这个时候线程就不能接受其他的操作请求,为了提供吞吐量,必须创建多个线程,每个线程去响应一个客户的请求,但是同一时间,一个cpu核心上面只能运行一个线程,多个线程要想执行就必须在不同的线程之间进行切换。
因此node.js少了多线程中线程的创建,以及线程的切换的开销,线程切换的代价是非常大的,需要为其分配内存,列入调度,同时在线程切换的时候需要执行内存换页等等操作,采用单线程的方式就可以减少这些操作。但是这种编程方式也有缺点,不符合人们的设计思维。
node.js是基于事件的模式来实现异步I/O的,当其启动之后会不停的遍历是否有为完成的事件,然后进行执行,执行完成之后会以另外一个事件的形式通知线程,本操作已经完成,这个事件又会被添加到未完成的事件列表中,线程在接下来的某个时刻遍历到这个事件然后进行执行,在这种机制中,需要将一个大的任务分成一个个小的事件,node.js也适合处理一些高I/O,低逻辑的场景。
下面的例子演示异步的文件读取:
var fs = require('fs');
fs.readFile('file.txt', 'utf-8', function(err, data) {
if (err) {
<span style="white-space:pre"> </span>console.error(err);
} else {
<span style="white-space:pre"> </span>console.log(data);
}
});
console.log("end");
如上fs.readFile异步读取文件,之后流程就会继续走,并不会等待其读取完文件,当文件读取完毕之后,会发布一个事件,执行线程遍历到该事件就会去执行对应的操作,这里是执行相应的回调函数,例子中字符串end会比文件内容先打印出来。
node.js的事件API
var events = require("events");
var emitter = new events.EventEmitter();
<span style="font-family: Arial, Helvetica, sans-serif;">emitter.on("eventName", function(){</span>
console.log("eventName事件发生")
})
事件的发布:
emitter.emit("eventName");
发布事件的时候我们可以传入多个参数,第一个参数表示事件的名称,其后的参数表示传入的参数,这些参数会被传入到事件的回调函数中。
EventEmitter.once("eventName", listener):为事件注册一个只执行一次的监听器,当事件第一次发生并触发监听器之后,该监听器就会解除,之后如果事件发生,该监听器不会执行。