Node.js 最大的特点就是异步式 I/O(或者非阻塞 I/O)与事件紧密结合的编程模式。这 种模式与传统的同步式 I/O 线性的编程思路有很大的不同,因为控制流很大程度上要靠事件 和回调函数来组织,一个逻辑要拆分为若干个单元
什么是阻塞(block)呢?线程在执行中如果遇到磁盘读写或网络通信(统称为 I/O 操作), 通常要耗费较长的时间,这时操作系统会剥夺这个线程的 CPU 控制权,使其暂停执行,同 时将资源让给其他的工作线程,这种线程调度方式称为 阻塞。当 I/O 操作完毕时,操作系统 将这个线程的阻塞状态解除,恢复其对CPU的控制权,令其继续执行。这种 I/O 模式就是通 常的同步式 I/O(Synchronous I/O)或阻塞式 I/O (Blocking I/O)。 4
相应地,异步式 I/O (Asynchronous I/O)或非阻塞式 I/O (Non-blocking I/O)则针对 所有 I/O 操作不采用阻塞的策略。当线程遇到 I/O 操作时,不会以阻塞的方式等待 I/O 操作 的完成或数据的返回,而只是将 I/O 请求发送给操作系统,继续执行下一条语句。当操作 系统完成 I/O 操作时,以事件的形式通知执行 I/O 操作的线程,线程会在特定时候处理这个 事件。为了处理异步 I/O,线程必须有事件循环,不断地检查有没有未处理的事件,依次予 以处理。
阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞 模式下,一个线程永远在执行计算操作,这个线程所使用的 CPU 核心利用率永远是 100%, I/O 以事件的方式通知。在阻塞模式下,多线程往往能提高系统吞吐量,因为一个线程阻塞 时还有其他线程在工作,多线程可以让 CPU 资源不被阻塞中的线程浪费。而在非阻塞模式 下,线程不会被 I/O 阻塞,永远在利用 CPU。多线程带来的好处仅仅是在多核 CPU 的情况 下利用更多的核,而Node.js的单线程也能带来同样的好处。这就是为什么 Node.js 使用了单 线程、非阻塞的事件编程模式
回调函数
//readfile.js
var fs = require('fs');
fs.readFile('file.txt', 'utf-8', function(err, data) {
if (err) { console.error(err);
} else { console.log(data);
} });
console.log('end.');
运行的结果如下:
end.
Contents of the file.
看到没有各位 先输出end 后才输出内容,为什么这样啊?因为是异步非阻塞的。function就是一个回调函数 。在 Node.js 中,异步式 I/O 是通过回调函数来实现的
fs.readFile 调用时所做的工作只是将异步式 I/O 请求发送给了操作系统,然后立即返回并执行后面的语句,执行完以后进入事件循环监听事件。当 fs 接收到 I/O 请求完成的事件时,事件循环会主动调用回调函数以完成后续工作。因此我们会先看到 end.,再看到file.txt 文件的内容
Node.js 在什么时候会进入事件循环呢?答案是 Node.js 程序由事件循环开始,到事件循环结束,所有的逻辑都是事件的回调函数,所以 Node.js 始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出 I/O 请求或直接发射(emit)事件,执行完毕后再返回事件循环,事件循环会检查事件队列中有没有未处理的事件,直到程序结束
模块和包
模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展
创建及加载模块
在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问 题仅仅在于如何在其他文件中获取这个模块
创建模块 module.js
var name;
exports.setName = function(mName){
name = mName;
};
exports.sayHello = function() {
console.log('Hello,' + name);
};
创建runModule.js里面代码如下:
var module = require('./module.js');
module.setName('greenboy');
module.sayHello();
在shell里面运行node runModule.js 产生结果如下:引入的时候就需要使用 require('./singleObject').Hello