Node.js快速入门
1 异步式I/O 与事件式编程
Node.js 最大的特点就是异步式I/O (或者非阻塞 I/O )与事件紧密结合的编程模式。这种模式与传统的同步式 I/O 线性的编程思路有很大的不同,因为控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元。
阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞
模式下,一个线程永远在执行计算操作,这个线程所使用的 CPU 核心利用率永远是 100%,
I/O 以事件的方式通知。在阻塞模式下,多线程往往能提高系统吞吐量,因为一个线程阻塞时还有其他线程在工作,多线程可以让 CPU 资源不被阻塞中的线程浪费。而在非阻塞模式下,线程不会被 I/O 阻塞,永远在利用 CPU。多线程带来的好处仅仅是在多核 CPU 的情况下利用更多的核,而Node.js的单线程也能带来同样的好处。这就是为什么 Node.js 使用了单线程、非阻塞的事件编程模式。
单线程事件驱动的异步式 I/O 比传统的多线程阻塞式 I/O 究竟好在哪里呢?简而言之,异步式I/O 就是少了多线程的开销。对操作系统来说,创建一个线程的代价是十分昂贵的,需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页,CPU 的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。
让我们看看在 Node.js 中如何用异步的方式读取一个文件:
var http = require("http"); http.createServer(function (req, res) { res.writeHead(200, { "Content-Type": "text/html" }); res.write('<h1>Node.js</h1>'); res.end("Hello World \n"); }).listen(8080, "127.0.0.1"); console.log("Server running at http://127.0.0.1:8080/");
运行的结果如下:
end.
Contents of thefile.
Node.js 也提供了同步读取文件的API :
//readfilesync.js var fs = require('fs'); var data = fs.readFileSync('file.txt', 'utf-8'); console.log(data); console.log('end.');
运行的结果与前面不同,如下所示:
$ nodereadfilesync.js
Contents of thefile.
end.
同步式读取文件的方式比较容易理解,将文件名作为参数传入 fs.readFileSync 函
数,阻塞等待读取完成后,将文件的内容作为函数的返回值赋给 data 变量,接下来控制台
输出 data 的值,最后输出 end. 。
异步式读取文件就稍微有些违反直觉了,end.先被输出。要想理解结果,我们必须先
知道在 Node.js 中,异步式I/O 是通过回调函数来实现的。fs.readFile 接收了三个参数,
第一个是文件名,第二个是编码方式,第三个是一个函数,我们称这个函数为回调函数。
JavaScript 支持匿名的函数定义方式,譬如我们例子中回调函数的定义就是嵌套在fs.readFile 的参数表中的。这种定义方式在 JavaScript 程序中极为普遍,与下面这种定义方式实现的功能是一致的:
/readfilecallback.js function readFileCallBack(err, data) { if (err) { console.error(err); } else { console.log(data); } } var fs = require('fs'); fs.readFile('file.txt', 'utf-8', readFileCallBack); console.log('end.');
fs.readFile 调用时所做的工作只是将异步式I/O 请求发送给了操作系统,然后立即返回并执行后面的语句,执行完以后进入事件循环监听事件。当 fs 接收到 I/O 请求完成的事件时,事件循环会主动调用回调函数以完成后续工作。因此我们会先看到 end. ,再看到file.txt 文件的内容。
2事件
Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。在开发者看来,事
件由 EventEmitter 对象提供。前面提到的 fs.readFile 和 http.createServer 的回调函数都是通过 EventEmitter 来实现的。下面我们用一个简单的例子说明 EventEmitter 的用法:
//event.js var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); event.on('some_event', function () { console.log('some_event occured.'); }); setTimeout( function () { event.emit('some_event'); }, 1000);
运行这段代码,1秒后控制台输出了 some_event occured. 。
其原理是 event 对象注册了事件 some_event 的一个监听器,然后我们通过 setTimeout 在1000 毫秒以后向 event 对象发送事件 some_event ,此时会调用some_event 的监听器。
Node.js 的事件循环机制
Node.js 在什么时候会进入事件循环呢?答案是Node.js 程序由事件循环开始,到事件循
环结束,所有的逻辑都是事件的回调函数,所以 Node.js 始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出 I/O 请求或直接发射(emit )事件,执行完毕后再返回事件循环,事件循环会检查事件队列中有没有未处理的事件,直到程序结束,下图说明了事件循环的原理。
3、模块和包
模块(Module)和包(Package)是 Node.js 最重要的支柱。开发一个具有一定规模的程序不可能只用一个文件,通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实现这种方式而诞生的。在浏览器 JavaScript 中,脚本模块的拆分和组合通常使用 HTML 的 script 标签来实现。Node.js提供了 require 函数来调用其他模块,而且模块都是基于
文件的,机制十分简单。
Node.js 的模块和包机制的实现参照了CommonJS 的标准,但并未完全遵循。不过
两者的区别并不大,一般来说你大可不必担心,只有当你试图制作一个除了支持 Node.js
之外还要支持其他平台的模块或包的时候才需要仔细研究。通常,两者没有直接冲突的
地方。
我们经常把 Node.js 的模块和包相提并论,因为模块和包是没有本质区别的,两个概念也时常混用。如果要辨析,那么可以把包理解成是实现了某个功能模块的集合,用于发布
和维护。对使用者来说,模块和包的区别是透明的,因此经常不作区分。
3.1、什么是模块
模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个
Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。
3.2 创建及加载模块
介绍了什么是模块之后,下面我们来看看如何创建并加载它们。在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问题仅仅在于如何在其他文件中获取这个模块。Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。
让我们以一个例子来了解模块。创建一个 module.js 的文件,内容是://module.js var name; exports.setName = function (thyName) { name = thyName; }; exports.sayHello = function () { console.log('Hello ' + name); };在同一目录下创建 getmodule.js ,内容是:
//getmodule.js var myModule = require('./module'); myModule.setName('BYVoid'); myModule.sayHello();
运行node getmodule.js,结果是:
Hello BYVoid
在以上示例中,module.js 通过 exports 对象把 setName 和 sayHello 作为模块的访问接口,在getmodule.js 中通过require('./module') 加载这个模块,然后就可以直接访问 module.js 中 exports 对象的成员函数了。
3.4、 创建包
包是在模块基础上更深一步的抽象,Node.js的包类似于 C/C++ 的函数库或者Java/.Net
的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Node.js 根
据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求。
Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件package.json 。严格符
合 CommonJS 规范的包应该具备以下特征:
package.json 必须在包的顶层目录下;
二进制文件应该在 bin 目录下;
JavaScript 代码应该在 lib 目录下;
文档应该在 doc 目录下;
单元测试应该在 test 目录下。
Node.js 对包的要求并没有这么严格,只要顶层目录下有 package.json ,并符合一些规范即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守 CommonJS 规范。
3.4.1. 作为文件夹的模块
模块与文件是一一对应的。文件不仅可以是JavaScript 代码或二进制代码,还可以是一个文件夹。最简单的包,就是一个作为文件夹的模块。下面我们来看一个例子,建立一个叫做package 的文件夹,在其中创建index.js ,内容如下:
//package/index.js exports.hello = function () { console.log('Hello world.'); };然后在 package 之外建立 getpackage.js ,内容如下:
//getpackage.js var package = require('./ package'); package.hello();
运行 node getpackage.js,控制台将输出结果 Hello world.。
我们使用这种方法可以把文件夹封装为一个模块,即所谓的包。包通常是一些模块的集
合,在模块的基础上提供了更高层的抽象,相当于提供了一些固定接口的函数库。通过定制
package.json,我们可以创建更复杂、更完善、更符合规范的包用于发布。
3.4.2. package.json
在前面例子中的package 文件夹下,我们创建一个叫做package.json 的文件,内容如
下所示:
{
"main" :"./lib/interface.js"
}
然后将 index.js 重命名为interface.js 并放入 lib 子文件夹下。以同样的方式再次调用这个包,依然可以正常使用。
Node.js 在调用某个包时,会首先检查包中 package.json 文件的 main 字段,将其作为包的接口模块,如果 package.json 或 main 字段不存在,会尝试寻找index.js 或 index.node 作为包的接口。
3.5 Node.js 包管理器
Node.js包管理器,即npm 是Node.js 官方提供的包管理工具,它已经成了Node.js 包的标准发布平台,用于Node.js 包的发布、传播、依赖控制。npm 提供了命令行工具,使你可以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。
3.5.1. 获取一个包
使用 npm 安装包的命令格式为:
npm [install/i][package_name]
例如你要安装 express ,可以在命令行运行:
$ npm install express
或者:
$ npm i express
随后你会看到以下安装信息:
npm http GEThttps://registry.npmjs.org/express
npm http 304https://registry.npmjs.org/express
npm http GEThttps://registry.npmjs.org/mime/1.2.4
npm http GEThttps://registry.npmjs.org/mkdirp/0.3.0
npm http GEThttps://registry.npmjs.org/qs
npm http GEThttps://registry.npmjs.org/connect
npm http 200https://registry.npmjs.org/mime/1.2.4
npm http 200https://registry.npmjs.org/mkdirp/0.3.0
npm http 200https://registry.npmjs.org/qs
npm http GEThttps://registry.npmjs.org/mime/-/mime-1.2.4.tgz
npm http GEThttps://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz
npm http 200https://registry.npmjs.org/mime/-/mime-1.2.4.tgz
npm http 200https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz
npm http 200https://registry.npmjs.org/connect
npm http GEThttps://registry.npmjs.org/formidable
npm http 200https://registry.npmjs.org/formidable
express@2.5.8./node_modules/express
-- mime@1.2.4
-- mkdirp@0.3.0
-- qs@0.4.2
-- connect@1.8.5
此时 express 就安装成功了,并且放置在当前目录的 node_modules 子目录下。npm 在npm 之于 Node.js ,就像 pip 之于 Python,gem 之于 Ruby ,pear 之于 PHP,CPAN 之于 Perl ……同时也像apt-get 之于 Debian/Ubutnu ,yum 之于 Fedora/RHEL/CentOS ,homebrew 之于 Mac OS X 。获取 express 的时候还将自动解析其依赖,并获取 express 依赖的 mime、mkdirp、qs 和 connect。
3.5.2. 本地模式和全局模式
npm 在默认情况下会从http://npmjs.org搜索或下载包,将包安装到当前目录的node_modules子目录下。
在使用 npm 安装包的时候,有两种模式:本地模式和全局模式。默认情况下我们使用 npm install命令就是采用本地模式,即把包安装到当前目录的 node_modules 子目录下。Node.js 的 require 在加载模块时会尝试搜寻 node_modules 子目录,因此使用 npm 本地模式安装的包可以直接被引用。
npm 还有另一种不同的安装模式被成为全局模式,使用方法为: npm [install/i] -g [package_name] 与本地模式的不同之处就在于多了一个参数-g。我们在 介绍 supervisor 那个小节中使用了 npm install -gsupervisor 命令,就是以全局模式安装supervisor 。
3.5.3包的发布
npm 可以非常方便地发布一个包,比 pip 、gem 、pear 要简单得多。在发布之前,首先需要让我们的包符合 npm 的规范,npm 有一套以 CommonJS 为基础包规范,但与 CommonJS 并不完全一致,其主要差别在于必填字段的不同。通过使用 npm init 可以根据交互式问答产生一个符合标准的package.json,例如创建一个名为 module 的目录,然后在这个目录中运行npm init :
$ npm init;根据提示完成输入后就可以了;
这样就在module 目录中生成一个符合npm 规范的 package.json 文件。创建一个 index.js 作为包的接口,一个简单的包就制作完成了。
在发布前,我们还需要获得一个账号用于今后维护自己的包,使用 npm adduser 根据提示输入用户名、密码、邮箱,等待账号创建完成。完成后可以使用 npm whoami 测验是否已经取得了账号。 接下来,在package.json 所在目录下运行 npmpublish ,稍等片刻就可以完成发布了。打开浏览器,访问 http://search.npmjs.org/ 就可以找到自己刚刚发布的包了。现在我们可以在世界的任意一台计算机上使用 npm install byvoidmodule 命令来安装它。 如果你的包将来有更新,只需要在 package.json 文件中修改 version 字段,然后重新使用 npm publish 命令就行了。如果你对已发布的包不满意(比如我们发布的这个毫无意义的包),可以使用 npm unpublish 命令来取消发布。
来自: http://blog.csdn.net//u011067360/article/details/17710071